diff --git a/pom.xml b/pom.xml
index a95702d..80de110 100644
--- a/pom.xml
+++ b/pom.xml
@@ -114,5 +114,6 @@
testideas-websockets
testideas-sgbd
testideas-jersey
+ testideas-terminalEmulator
diff --git a/testideas-terminalEmulator/.classpath b/testideas-terminalEmulator/.classpath
new file mode 100644
index 0000000..166d544
--- /dev/null
+++ b/testideas-terminalEmulator/.classpath
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/testideas-terminalEmulator/.gitignore b/testideas-terminalEmulator/.gitignore
new file mode 100644
index 0000000..b83d222
--- /dev/null
+++ b/testideas-terminalEmulator/.gitignore
@@ -0,0 +1 @@
+/target/
diff --git a/testideas-terminalEmulator/.project b/testideas-terminalEmulator/.project
new file mode 100644
index 0000000..b616feb
--- /dev/null
+++ b/testideas-terminalEmulator/.project
@@ -0,0 +1,23 @@
+
+
+ testideas-terminalEmulator
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.m2e.core.maven2Builder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+ org.eclipse.m2e.core.maven2Nature
+
+
diff --git a/testideas-terminalEmulator/.settings/org.eclipse.core.resources.prefs b/testideas-terminalEmulator/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 0000000..29abf99
--- /dev/null
+++ b/testideas-terminalEmulator/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,6 @@
+eclipse.preferences.version=1
+encoding//src/main/java=UTF-8
+encoding//src/main/resources=UTF-8
+encoding//src/test/java=UTF-8
+encoding//src/test/resources=UTF-8
+encoding/=UTF-8
diff --git a/testideas-terminalEmulator/.settings/org.eclipse.jdt.core.prefs b/testideas-terminalEmulator/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..2f5cc74
--- /dev/null
+++ b/testideas-terminalEmulator/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,8 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
+org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.release=disabled
+org.eclipse.jdt.core.compiler.source=1.8
diff --git a/testideas-terminalEmulator/.settings/org.eclipse.m2e.core.prefs b/testideas-terminalEmulator/.settings/org.eclipse.m2e.core.prefs
new file mode 100644
index 0000000..f897a7f
--- /dev/null
+++ b/testideas-terminalEmulator/.settings/org.eclipse.m2e.core.prefs
@@ -0,0 +1,4 @@
+activeProfiles=
+eclipse.preferences.version=1
+resolveWorkspaceProjects=true
+version=1
diff --git a/testideas-terminalEmulator/README.md b/testideas-terminalEmulator/README.md
new file mode 100644
index 0000000..563d635
--- /dev/null
+++ b/testideas-terminalEmulator/README.md
@@ -0,0 +1,90 @@
+Test Ideas : Terminal Emulator
+
+A complete Unix/Linux terminal emulator implemented in Java 8 with Swing GUI. (2 versions !)
+
+## Version 1
+
+Features:
+- Virtual file system with directories and text files
+- 14+ Unix-like commands (ls, cd, pwd, cat, echo, touch, mkdir, rm, rmdir, cp, mv, clear, help, exit)
+- Minimalist scripting language with variables, conditions, and loops
+- Swing-based graphical user interface
+- Command history with up/down arrow navigation
+- Basic tab completion
+- Comprehensive unit and integration tests (JUnit 5)
+
+Usage:
+1. Compile: javac -d bin src/com/terminal/emulator/**/*.java
+2. Run: java -cp bin com.terminal.emulator.Main
+3. Type 'help' for available commands
+4. Type 'exit' to quit
+
+Project Structure:
+com.terminal/
+ - Main.java (entry point)
+ - TerminalState.java (global state)
+ - gui/ (Swing GUI classes)
+ - filesystem/ (Directory, TerminalFile, FileNode)
+ - commands/ (Command interface and implementations)
+ - script/ (ScriptEngine, ScriptParser, ScriptContext)
+
+tests/
+ - filesystem/ (FileSystemTest, DirectoryTest)
+ - commands/ (CommandParserTest, LsCommandTest, CdCommandTest, CommandFactoryTest)
+ - TerminalStateTest.java
+ - ScriptEngineTest.java
+ - IntegrationTest.java
+
+```
+Commands Available:
+ls [OPTIONS] [FILE...] - List directory contents
+cd [DIR] - Change directory
+pwd - Print working directory
+cat [FILE...] - Concatenate and print files
+echo [STRING...] - Display a line of text
+touch [FILE...] - Create empty files
+mkdir [DIRECTORY...] - Create directories
+rm [FILE...] - Remove files
+rmdir [DIRECTORY...] - Remove empty directories
+cp SOURCE DEST - Copy files
+mv SOURCE DEST - Move or rename files
+clear - Clear the terminal screen
+help [COMMAND] - Display help information
+exit - Exit the terminal
+
+Scripting Language:
+# Variables
+set var=value
+echo $var
+
+# Conditions
+if [ "$var" = "value" ]
+ echo Equal
+fi
+
+# File tests
+if [ -f "file.txt" ]
+ echo File exists
+fi
+
+# While loops
+set count=0
+while [ "$count" != "5" ]
+ echo $count
+ # Note: Manual incrementation needed in this version
+ set count=1
+done
+
+# For loops
+for item in a b c
+ echo $item
+done
+
+Note: The scripting engine is minimal and does not support all Unix shell features.
+```
+
+## Version 2
+
+Adding rediretiuons, pipes, better scripting... !
+
+...
\ No newline at end of file
diff --git a/testideas-terminalEmulator/pom.xml b/testideas-terminalEmulator/pom.xml
new file mode 100644
index 0000000..a3c0b33
--- /dev/null
+++ b/testideas-terminalEmulator/pom.xml
@@ -0,0 +1,111 @@
+
+
+ 4.0.0
+
+ gabywald
+ testideas
+ 0.0.1-SNAPSHOT
+
+ testideas-terminalEmulator
+ jar
+ testideas-terminalEmulator
+ Unix/Linux terminal emulator in Java 8 with Swing
+
+ UTF-8
+ 1.8
+ 1.8
+ 5.8.2
+
+
+
+
+
+ info.picocli
+ picocli
+ 4.7.7
+ compile
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+
+
+
+ src/main/java
+ src/test/java
+
+
+ src/main/resources
+
+ **/*
+
+
+
+
+
+ src/test/resources
+
+ **/*
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.2.0
+
+
+
+ gabywald.launchers.TerminalEmulatorLaunchers
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.6.0
+
+
+
+
+ gabywald.launchers.TerminalEmulatorLaunchers
+
+ true
+
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/launchers/TerminalEmulatorCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/launchers/TerminalEmulatorCommand.java
new file mode 100644
index 0000000..74eb733
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/launchers/TerminalEmulatorCommand.java
@@ -0,0 +1,101 @@
+package gabywald.launchers;
+
+import gabywald.terminal.gui.TerminalFrame;
+import gabywald.terminal2.TerminalEmulator;
+import gabywald.utilities.logger.Logger;
+import gabywald.utilities.logger.Logger.LoggerLevel;
+import picocli.CommandLine.ArgGroup;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/**
+ *
+ * @author Gabriel Chandesris (2026)
+ */
+@Command(
+ name = "testideas-terminalemulator",
+ version = "1.0",
+ description = "Application CLI with picocli.",
+ // subcommands = { EncodeCommand.class, DecodeCommand.class },
+ mixinStandardHelpOptions = true)
+public class TerminalEmulatorCommand implements Runnable {
+
+ /**
+ * Code Command.
+ */
+ static class Version {
+ enum TheEnum { version1, version2 }
+
+ TheEnum actualValue = TheEnum.version2;
+
+ @Option(names = {"-v1", "--version1"},
+ description = "First Version of Terminal Emulator. ")
+ void setVersion1(boolean b) { this.actualValue = TheEnum.version1; }
+
+ @Option(names = {"-v2", "--version2"},
+ description = "Second Version of Terminal Emulator. ")
+ void setVersion2(boolean b) { this.actualValue = TheEnum.version2; }
+
+ boolean isVersion1() { return (this.actualValue == TheEnum.version1); }
+ boolean isVersion2() { return (this.actualValue == TheEnum.version2); }
+ }
+ @ArgGroup(exclusive = true, heading = "Version Options%n", multiplicity = "1")
+ Version codVersion = new Version();
+
+ /**
+ * Log Level.
+ */
+ public static class LogLevel {
+ // public enum TheEnum { none, error, warn, info, debug, trace }
+ public enum TheEnum { trace, debug, info, warn, error, none }
+
+ TheEnum actualValue = TheEnum.none;
+
+ @Option(names = "--debug", description = "Sets log level to DEBUG.")
+ void setDebug(boolean b) { this.actualValue = TheEnum.debug; }
+
+ @Option(names = "--info", description = "Sets log level to INFO.")
+ void setInfo(boolean b) { this.actualValue = TheEnum.info; }
+
+ @Option(names = "--warn", description = "Sets log level to WARN.")
+ void setWarn(boolean b) { this.actualValue = TheEnum.warn; }
+
+ @Option(names = "--error", description = "Sets log level to ERROR.")
+ void setError(boolean b) { this.actualValue = TheEnum.error; }
+
+ @Option(names = "--trace", description = "Sets log level to NONE.")
+ void setTrace(boolean b) { this.actualValue = TheEnum.trace; }
+
+ @Option(names = "--none", description = "Sets log level to NONE.")
+ void setNone(boolean b) { this.actualValue = TheEnum.none; }
+
+ }
+ @ArgGroup(exclusive = true, heading = "Log Level Options%n", multiplicity = "0..1")
+ LogLevel logLevel = new LogLevel();
+
+ public boolean isLogEnabled(LogLevel.TheEnum checkValue) {
+ return checkValue.ordinal() >= logLevel.actualValue.ordinal();
+ }
+
+ public LogLevel.TheEnum setLogLevel(LogLevel.TheEnum nextValue) {
+ LogLevel.TheEnum prevValue = this.logLevel.actualValue;
+ this.logLevel.actualValue = nextValue;
+ return prevValue;
+ }
+
+ @Override
+ public void run() {
+ if (codVersion.isVersion1()) {
+ Logger.printlnLog(LoggerLevel.LL_FORUSER, "Terminal Emulator Version 1. ");
+ TerminalFrame frame = new TerminalFrame();
+ frame.setVisible(true);
+ }
+ else if (codVersion.isVersion2()) {
+ Logger.printlnLog(LoggerLevel.LL_FORUSER, "Terminal Emulator Version 2. ");
+ new TerminalEmulator();
+ }
+ else
+ { Logger.printlnLog(LoggerLevel.LL_WARNING, "Terminal Emulator UNKNOWN VERSION. "); }
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/launchers/TerminalEmulatorLaunchers.java b/testideas-terminalEmulator/src/main/java/gabywald/launchers/TerminalEmulatorLaunchers.java
new file mode 100644
index 0000000..dd20308
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/launchers/TerminalEmulatorLaunchers.java
@@ -0,0 +1,21 @@
+package gabywald.launchers;
+
+import gabywald.utilities.logger.Logger;
+import gabywald.utilities.logger.Logger.LoggerLevel;
+import picocli.CommandLine;
+
+/**
+ *
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalEmulatorLaunchers {
+
+ public static void main(String[] args) {
+ TerminalEmulatorCommand tec = new TerminalEmulatorCommand();
+ int exitCode = new CommandLine(tec).execute(args);
+ // System.exit(exitCode);
+ if (tec.isLogEnabled(TerminalEmulatorCommand.LogLevel.TheEnum.info))
+ { Logger.printlnLog(LoggerLevel.LL_FORUSER, exitCode + "" ); }
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/MainTerminal.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/MainTerminal.java
new file mode 100644
index 0000000..57f255a
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/MainTerminal.java
@@ -0,0 +1,15 @@
+package gabywald.terminal;
+
+import gabywald.terminal.gui.TerminalFrame;
+
+/**
+ * Main entry point for the terminal emulator application.
+ * @author Gabriel Chandesris (2026)
+ * @deprecated Use {@code gabywald.launchers.TerminalEmulatorLaunchers}
+ */
+public class MainTerminal {
+ public static void main(String[] args) {
+ TerminalFrame frame = new TerminalFrame();
+ frame.setVisible(true);
+ }
+}
\ No newline at end of file
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/TerminalState.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/TerminalState.java
new file mode 100644
index 0000000..b7c8134
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/TerminalState.java
@@ -0,0 +1,84 @@
+package gabywald.terminal;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import gabywald.terminal.filesystem.TerminalDirectory;
+
+/**
+ * Global state of the terminal including current directory, history, etc.
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalState {
+ private TerminalDirectory currentDirectory;
+ private TerminalDirectory rootDirectory;
+ private List commandHistory;
+ private int historyIndex;
+ private String prompt;
+
+ public TerminalState() {
+ this.rootDirectory = new TerminalDirectory("/", null);
+ this.currentDirectory = this.rootDirectory;
+ this.commandHistory = new ArrayList<>();
+ this.historyIndex = -1;
+ this.prompt = "user@terminal:~$";
+ }
+
+ public TerminalState(TerminalDirectory root) {
+ this.rootDirectory = root;
+ this.currentDirectory = root;
+ this.commandHistory = new ArrayList<>();
+ this.historyIndex = -1;
+ this.prompt = "user@terminal:~$";
+ }
+
+ public TerminalDirectory getCurrentDirectory() { return currentDirectory; }
+ public void setCurrentDirectory(TerminalDirectory currentDirectory) {
+ this.currentDirectory = currentDirectory;
+ updatePrompt();
+ }
+ public TerminalDirectory getRootDirectory() { return this.rootDirectory; }
+ public List getCommandHistory() { return new ArrayList(commandHistory); }
+
+ public void addToHistory(String command) {
+ if (command != null && !command.trim().isEmpty()) {
+ this.commandHistory.add(command);
+ this.historyIndex = this.commandHistory.size();
+ }
+ }
+
+ public int getHistoryIndex() { return historyIndex; }
+ public void setHistoryIndex(int historyIndex) { this.historyIndex = historyIndex; }
+ public String getPrompt() { return this.prompt; }
+
+ private void updatePrompt() {
+ String path = this.currentDirectory.getPath();
+ if ("/".equals(path)) { this.prompt = "user@terminal:~$"; }
+ else { this.prompt = "user@terminal:" + path + "$"; }
+ }
+
+ public String getPreviousCommand() {
+ if (this.commandHistory.isEmpty()) return null;
+ if (this.historyIndex > 0) {
+ this.historyIndex--;
+ return this.commandHistory.get(this.historyIndex);
+ } else if (this.historyIndex == 0) {
+ return this.commandHistory.get(0);
+ }
+ return null;
+ }
+
+ public String getNextCommand() {
+ if (this.commandHistory.isEmpty()) return null;
+ if (this.historyIndex < commandHistory.size() - 1) {
+ this.historyIndex++;
+ return this.commandHistory.get(this.historyIndex);
+ } else if (this.historyIndex == this.commandHistory.size() - 1) {
+ this.historyIndex = this.commandHistory.size();
+ return "";
+ }
+ return null;
+ }
+
+ public void resetHistoryIndex() { this.historyIndex = this.commandHistory.size(); }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CatCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CatCommand.java
new file mode 100644
index 0000000..973b5e6
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CatCommand.java
@@ -0,0 +1,39 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalNode;
+import gabywald.terminal.filesystem.TerminalFile;
+
+/**
+ * cat command - Concatenate and print files
+ * @author Gabriel Chandesris (2026)
+ */
+public class CatCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0) return "cat: missing operand";
+
+ StringBuilder output = new StringBuilder();
+ for (String fileName : args) {
+ TerminalNode node = CommandHelper.resolveFile(state, fileName);
+ if (node == null) {
+ output.append("cat: ").append(fileName).append(": No such file or directory\n");
+ continue;
+ }
+ if (!node.isFile()) {
+ output.append("cat: ").append(fileName).append(": Is a directory\n");
+ continue;
+ }
+ TerminalFile file = (TerminalFile) node;
+ output.append(file.getContent());
+ }
+ return output.toString();
+ }
+
+ @Override
+ public String getName() { return "cat"; }
+ @Override
+ public String getDescription() { return "Concatenate and print files"; }
+ @Override
+ public String getUsage() { return "cat [FILE]..."; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CdCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CdCommand.java
new file mode 100644
index 0000000..139237d
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CdCommand.java
@@ -0,0 +1,34 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalDirectory;
+
+/**
+ * cd command - Change directory
+ * @author Gabriel Chandesris (2026)
+ */
+public class CdCommand implements Command {
+
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0) {
+ state.setCurrentDirectory(state.getRootDirectory());
+ return "";
+ }
+
+ String path = args[0];
+ TerminalDirectory newDir = CommandHelper.resolveDirectory(state, path);
+ if (newDir == null) { return "cd: no such file or directory: " + path; }
+
+ state.setCurrentDirectory(newDir);
+ return "";
+ }
+
+ @Override
+ public String getName() { return "cd"; }
+ @Override
+ public String getDescription() { return "Change the current directory"; }
+ @Override
+ public String getUsage() { return "cd [DIR]"; }
+
+}
\ No newline at end of file
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/ClearCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/ClearCommand.java
new file mode 100644
index 0000000..0d45be7
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/ClearCommand.java
@@ -0,0 +1,21 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+
+/**
+ * clear command - Clear the terminal screen
+ * @author Gabriel Chandesris (2026)
+ */
+public class ClearCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ return "\033[H\033[2J";
+ }
+
+ @Override
+ public String getName() { return "clear"; }
+ @Override
+ public String getDescription() { return "Clear the terminal screen"; }
+ @Override
+ public String getUsage() { return "clear"; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/Command.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/Command.java
new file mode 100644
index 0000000..b8f9cef
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/Command.java
@@ -0,0 +1,14 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+
+/**
+ * Command interface for all executable commands.
+ * @author Gabriel Chandesris (2026)
+ */
+public interface Command {
+ String execute(TerminalState state, String[] args);
+ String getName();
+ String getDescription();
+ String getUsage();
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandFactory.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandFactory.java
new file mode 100644
index 0000000..77a7887
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandFactory.java
@@ -0,0 +1,44 @@
+package gabywald.terminal.commands;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Factory for creating and registering available commands.
+ * @author Gabriel Chandesris (2026)
+ */
+public class CommandFactory {
+
+ private static final Map commands = new HashMap();
+
+ static {
+ CommandFactory.registerCommand(new LsCommand());
+ CommandFactory.registerCommand(new CdCommand());
+ CommandFactory.registerCommand(new PwdCommand());
+ CommandFactory.registerCommand(new CatCommand());
+ CommandFactory.registerCommand(new EchoCommand());
+ CommandFactory.registerCommand(new TouchCommand());
+ CommandFactory.registerCommand(new MkdirCommand());
+ CommandFactory.registerCommand(new RmCommand());
+ CommandFactory.registerCommand(new RmdirCommand());
+ CommandFactory.registerCommand(new CpCommand());
+ CommandFactory.registerCommand(new MvCommand());
+ CommandFactory.registerCommand(new ClearCommand());
+ CommandFactory.registerCommand(new HelpCommand());
+ CommandFactory.registerCommand(new ExitCommand());
+ }
+
+ public static void registerCommand(Command command) {
+ if (command != null) { CommandFactory.commands.put(command.getName().toLowerCase(), command); }
+ }
+
+ public static Command getCommand(String name) {
+ if (name == null || name.isEmpty()) { return null; }
+ return CommandFactory.commands.get(name.toLowerCase());
+ }
+
+ public static boolean hasCommand(String name) { return CommandFactory.getCommand(name) != null; }
+ public static Map getAllCommands() { return new HashMap<>(CommandFactory.commands); }
+ public static String[] getCommandNames() { return CommandFactory.commands.keySet().toArray(new String[0]); }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandHelper.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandHelper.java
new file mode 100644
index 0000000..6abe777
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandHelper.java
@@ -0,0 +1,144 @@
+package gabywald.terminal.commands;
+
+import java.time.format.DateTimeFormatter;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalNode;
+import gabywald.terminal.filesystem.TerminalDirectory;
+
+/**
+ *
+ * @author Gabriel Chandesris (2026)
+ */
+public abstract class CommandHelper {
+
+ static TerminalDirectory getParentFromAbsolutePath(TerminalDirectory root, String path) {
+ String[] parts = path.split("/");
+ TerminalDirectory current = root;
+ for (int i = 1; i < parts.length - 1; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) { continue; }
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) { current = current.getParent(); }
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null || !node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ static TerminalDirectory getParentFromRelativePath(TerminalDirectory current, String path) {
+ String[] parts = path.split("/");
+ for (int i = 0; i < parts.length - 1; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) { continue; }
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) current = current.getParent();
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null || !node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ static TerminalNode resolveFile(TerminalState state, String fileName) {
+ TerminalDirectory current = state.getCurrentDirectory();
+ if (fileName.startsWith("/")) { return CommandHelper.resolveAbsolutePath(state.getRootDirectory(), fileName); }
+ return CommandHelper.resolveRelativePath(current, fileName);
+ }
+
+ static TerminalNode resolveAbsolutePath(TerminalDirectory root, String path) {
+ String[] parts = path.split("/");
+ TerminalDirectory current = root;
+ for (int i = 1; i < parts.length; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) { continue; }
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) { current = current.getParent(); }
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null) { return null; }
+ if (i == parts.length - 1) { return node; }
+ if (!node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ static TerminalNode resolveRelativePath(TerminalDirectory current, String path) {
+ String[] parts = path.split("/");
+ for (int i = 0; i < parts.length; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) { continue; }
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) { current = current.getParent(); }
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null) { return null; }
+ if (i == parts.length - 1) { return node; }
+ if (!node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ static TerminalDirectory resolveDirectory(TerminalState state, String dirName) {
+ TerminalDirectory current = state.getCurrentDirectory();
+ if (dirName.startsWith("/")) { return CommandHelper.resolveAbsoluteDirectory(state.getRootDirectory(), dirName); }
+ return CommandHelper.resolveRelativeDirectory(current, dirName);
+ }
+
+ static TerminalDirectory resolveAbsoluteDirectory(TerminalDirectory root, String path) {
+ String[] parts = path.split("/");
+ TerminalDirectory current = root;
+ for (int i = 1; i < parts.length; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) { continue; }
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) { current = current.getParent(); }
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null || !node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ static TerminalDirectory resolveRelativeDirectory(TerminalDirectory current, String path) {
+ String[] parts = path.split("/");
+ for (String part : parts) {
+ if (part.isEmpty() || ".".equals(part)) { continue; }
+ if ("..".equals(part)) {
+ if (current.getParent() != null) { current = current.getParent(); }
+ } else {
+ TerminalNode node = current.getChild(part);
+ if (node == null || !node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ static TerminalDirectory getParentDirectory(TerminalState state, String path) {
+ if (path.startsWith("/")) { return CommandHelper.getParentFromAbsolutePath(state.getRootDirectory(), path); }
+ return CommandHelper.getParentFromRelativePath(state.getCurrentDirectory(), path);
+ }
+
+ static String getSimpleName(String path) {
+ String[] parts = path.split("/");
+ return parts[parts.length - 1];
+ }
+
+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+
+ static String formatLong(TerminalNode node) {
+ String type = node.isDirectory() ? "d" : "-";
+ String permissions = node.isDirectory() ? "rwxr-xr-x" : "rw-r--r--";
+ String size = node.isDirectory() ? "4096" : String.valueOf(node.getSize());
+ String date = node.getModifiedAt().format(DATE_FORMATTER);
+ return String.format("%s%s 1 user group %8s %s %s\n", type, permissions, size, date, node.getName());
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandParser.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandParser.java
new file mode 100644
index 0000000..1461447
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CommandParser.java
@@ -0,0 +1,59 @@
+package gabywald.terminal.commands;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Command parser for extracting command name and arguments.
+ * Handles quoted arguments with spaces.
+ * @author Gabriel Chandesris (2026)
+ */
+public class CommandParser {
+
+ public static String[] parse(String input) {
+ if (input == null || input.trim().isEmpty()) return new String[0];
+
+ String trimmed = input.trim();
+ List tokens = new ArrayList<>();
+ boolean inQuotes = false;
+ StringBuilder current = new StringBuilder();
+
+ for (int i = 0; i < trimmed.length(); i++) {
+ char c = trimmed.charAt(i);
+ if (c == '"') {
+ inQuotes = !inQuotes;
+ } else if (Character.isWhitespace(c) && !inQuotes) {
+ if (current.length() > 0) {
+ tokens.add(current.toString());
+ current.setLength(0);
+ }
+ } else { current.append(c); }
+ }
+
+ if (current.length() > 0) tokens.add(current.toString());
+ return tokens.toArray(new String[0]);
+ }
+
+ public static String getCommandName(String input) {
+ String[] parts = parse(input);
+ return parts.length > 0 ? parts[0] : "";
+ }
+
+ public static String[] getArguments(String input) {
+ String[] parts = parse(input);
+ if (parts.length > 1) {
+ String[] args = new String[parts.length - 1];
+ System.arraycopy(parts, 1, args, 0, args.length);
+ return args;
+ }
+ return new String[0];
+ }
+
+ public static boolean hasArguments(String input) { return getArguments(input).length > 0; }
+
+ public static String cleanArgument(String arg) {
+ if (arg == null) { return ""; }
+ return arg.replaceAll("^$", "").replaceAll("\"", ""); // NOTE "^\"|"$"
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CpCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CpCommand.java
new file mode 100644
index 0000000..bf0562c
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/CpCommand.java
@@ -0,0 +1,63 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalDirectory;
+import gabywald.terminal.filesystem.TerminalNode;
+import gabywald.terminal.filesystem.TerminalFile;
+
+/**
+ * cp command - Copy filesabriel Chandesris (2026)
+ * @author Gabriel Chandesris (2026)
+ */
+public class CpCommand implements Command {
+
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length < 2) {
+ return "cp: missing destination file operand after '" +
+ (args.length > 0 ? args[args.length - 1] : "") + "'\nTry 'cp --help' for more information.";
+ }
+
+ if (args.length == 2) { return copyFile(state, args[0], args[1]); }
+
+ String destination = args[args.length - 1];
+ TerminalDirectory destDir = CommandHelper.resolveDirectory(state, destination);
+ if (destDir == null || !destDir.isDirectory()) { return "cp: target '" + destination + "' is not a directory"; }
+
+ StringBuilder output = new StringBuilder();
+ for (int i = 0; i < args.length - 1; i++) {
+ String result = copyFile(state, args[i], destination + "/" + CommandHelper.getSimpleName(args[i]));
+ if (!result.isEmpty()) { output.append(result).append("\n"); }
+ }
+ return output.toString();
+ }
+
+ private String copyFile(TerminalState state, String sourcePath, String destPath) {
+ TerminalNode source = CommandHelper.resolveFile(state, sourcePath);
+ if (source == null) return "cp: cannot stat '" + sourcePath + "': No such file or directory";
+ if (source.isDirectory()) return "cp: -r not specified; omitting directory '" + sourcePath + "'";
+
+ TerminalDirectory destParent = CommandHelper.getParentDirectory(state, destPath);
+ if (destParent == null) return "cp: cannot create '" + destPath + "': No such file or directory";
+
+ String destName = CommandHelper.getSimpleName(destPath);
+ if (destParent.hasChild(destName)) {
+ TerminalNode existing = destParent.getChild(destName);
+ if (existing.isDirectory()) { return "cp: cannot overwrite directory '" + destPath + "' with '" + sourcePath + "'"; }
+ ((TerminalFile) existing).setContent(((TerminalFile) source).getContent());
+ } else {
+ TerminalFile sourceFile = (TerminalFile) source;
+ TerminalFile newFile = new TerminalFile(destName, destParent, sourceFile.getContent());
+ destParent.addChild(newFile);
+ }
+ return "";
+ }
+
+ @Override
+ public String getName() { return "cp"; }
+ @Override
+ public String getDescription() { return "Copy files and directories"; }
+ @Override
+ public String getUsage() { return "cp [OPTION] SOURCE DEST\n cp [OPTION] SOURCE... DIRECTORY"; }
+
+}
\ No newline at end of file
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/EchoCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/EchoCommand.java
new file mode 100644
index 0000000..985d929
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/EchoCommand.java
@@ -0,0 +1,27 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+
+/**
+ * echo command - Display a line of text
+ * @author Gabriel Chandesris (2026)
+ */
+public class EchoCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0) return "";
+ StringBuilder output = new StringBuilder();
+ for (int i = 0; i < args.length; i++) {
+ if (i > 0) { output.append(" "); }
+ output.append(args[i]);
+ }
+ return output.toString();
+ }
+
+ @Override
+ public String getName() { return "echo"; }
+ @Override
+ public String getDescription() { return "Display a line of text"; }
+ @Override
+ public String getUsage() { return "echo [STRING]..."; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/ExitCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/ExitCommand.java
new file mode 100644
index 0000000..1604c25
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/ExitCommand.java
@@ -0,0 +1,23 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+
+/**
+ * exit command - Exit the terminal
+ * @author Gabriel Chandesris (2026)
+ */
+public class ExitCommand implements Command {
+
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ return "EXIT";
+ }
+
+ @Override
+ public String getName() { return "exit"; }
+ @Override
+ public String getDescription() { return "Exit the terminal"; }
+ @Override
+ public String getUsage() { return "exit"; }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/HelpCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/HelpCommand.java
new file mode 100644
index 0000000..abbc0b6
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/HelpCommand.java
@@ -0,0 +1,45 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import java.util.Map;
+
+/**
+ * help command - Display help information
+ */
+public class HelpCommand implements Command {
+
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0)
+ { return this.getGeneralHelp(); }
+ else { return this.getCommandHelp(args[0]); }
+ }
+
+ private String getGeneralHelp() {
+ StringBuilder help = new StringBuilder();
+ help.append("Available commands:\n\n");
+ Map commands = CommandFactory.getAllCommands();
+ for (Map.Entry entry : commands.entrySet()) {
+ Command cmd = entry.getValue();
+ help.append(String.format(" %-15s %s\n", cmd.getName(), cmd.getDescription()));
+ }
+ help.append("\nType 'help ' for more information about a specific command.\n");
+ return help.toString();
+ }
+
+ private String getCommandHelp(String commandName) {
+ Command cmd = CommandFactory.getCommand(commandName);
+ if (cmd == null)
+ { return "help: no help topics match '" + commandName + "'.\nTry 'help' for a list of available commands."; }
+ return String.format("Usage: %s\n\n%s\n", cmd.getUsage(), cmd.getDescription());
+ }
+
+ @Override
+ public String getName() { return "help"; }
+ @Override
+ public String getDescription() { return "Display help information"; }
+ @Override
+ public String getUsage() { return "help [COMMAND]"; }
+
+}
+
\ No newline at end of file
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/LsCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/LsCommand.java
new file mode 100644
index 0000000..fa3c7a7
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/LsCommand.java
@@ -0,0 +1,67 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalDirectory;
+import gabywald.terminal.filesystem.TerminalNode;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * ls command - List directory contents
+ * @author Gabriel Chandesris (2026)
+ */
+public class LsCommand implements Command {
+
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ boolean longFormat = false;
+ boolean showHidden = false;
+ boolean reverseOrder = false;
+ String targetPath = ".";
+
+ List paths = new ArrayList<>();
+ for (String arg : args) {
+ if (arg.startsWith("-")) {
+ if (arg.contains("l")) { longFormat = true; }
+ if (arg.contains("a")) { showHidden = true; }
+ if (arg.contains("r")) { reverseOrder = true; }
+ } else if (!arg.isEmpty()) { paths.add(arg); }
+ }
+
+ if (!paths.isEmpty()) { targetPath = paths.get(0); }
+
+ TerminalDirectory targetDir = CommandHelper.resolveDirectory(state, targetPath);
+ if (targetDir == null) { return "ls: cannot access '" + targetPath + "': No such file or directory"; }
+
+ List children = new ArrayList<>(targetDir.getChildren());
+ if (!showHidden) { children.removeIf(node -> node.getName().startsWith(".")); }
+
+ final boolean reverseO = reverseOrder;
+ Collections.sort(children, (n1, n2) -> {
+ int result = n1.getName().compareTo(n2.getName());
+ return (reverseO ? -result : result);
+ });
+
+ StringBuilder output = new StringBuilder();
+ if (longFormat) {
+ output.append("total ").append(children.size()).append("\n");
+ for (TerminalNode node : children) output.append(CommandHelper.formatLong(node));
+ } else {
+ for (int i = 0; i < children.size(); i++) {
+ if (i > 0) { output.append(" "); }
+ output.append(children.get(i).getName());
+ }
+ if (!children.isEmpty()) { output.append("\n"); }
+ }
+ return output.toString();
+ }
+
+ @Override
+ public String getName() { return "ls"; }
+ @Override
+ public String getDescription() { return "List directory contents"; }
+ @Override
+ public String getUsage() { return "ls [OPTION]... [FILE]..."; }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/MkdirCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/MkdirCommand.java
new file mode 100644
index 0000000..f72416f
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/MkdirCommand.java
@@ -0,0 +1,41 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalDirectory;
+
+/**
+ * mkdir command - Create directories
+ * @author Gabriel Chandesris (2026)
+ */
+public class MkdirCommand implements Command {
+
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0) return "mkdir: missing operand";
+
+ StringBuilder output = new StringBuilder();
+ for (String dirName : args) {
+ TerminalDirectory parentDir = CommandHelper.getParentDirectory(state, dirName);
+ if (parentDir == null) {
+ output.append("mkdir: cannot create directory '").append(dirName).append("': No such file or directory\n");
+ continue;
+ }
+
+ String simpleName = CommandHelper.getSimpleName(dirName);
+ if (parentDir.hasChild(simpleName)) {
+ output.append("mkdir: cannot create directory '").append(dirName).append("': File exists\n");
+ continue;
+ }
+ parentDir.createDirectory(simpleName);
+ }
+ return output.toString();
+ }
+
+ @Override
+ public String getName() { return "mkdir"; }
+ @Override
+ public String getDescription() { return "Create directories"; }
+ @Override
+ public String getUsage() { return "mkdir DIRECTORY..."; }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/MvCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/MvCommand.java
new file mode 100644
index 0000000..c9daeb0
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/MvCommand.java
@@ -0,0 +1,66 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalDirectory;
+import gabywald.terminal.filesystem.TerminalNode;
+import gabywald.terminal.filesystem.TerminalFile;
+
+/**
+ * mv command - Move or rename files
+ * @author Gabriel Chandesris (2026)
+ */
+public class MvCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length < 2) {
+ return "mv: missing destination file operand after '" +
+ (args.length > 0 ? args[args.length - 1] : "") + "'\nTry 'mv --help' for more information.";
+ }
+
+ if (args.length == 2) {
+ return moveFile(state, args[0], args[1]);
+ }
+
+ String destination = args[args.length - 1];
+ TerminalDirectory destDir = CommandHelper.resolveDirectory(state, destination);
+ if (destDir == null || !destDir.isDirectory()) {
+ return "mv: target '" + destination + "' is not a directory";
+ }
+
+ StringBuilder output = new StringBuilder();
+ for (int i = 0; i < args.length - 1; i++) {
+ String result = moveFile(state, args[i], destination + "/" + CommandHelper.getSimpleName(args[i]));
+ if (!result.isEmpty()) { output.append(result).append("\n"); }
+ }
+ return output.toString();
+ }
+
+ private String moveFile(TerminalState state, String sourcePath, String destPath) {
+ TerminalNode source = CommandHelper.resolveFile(state, sourcePath);
+ if (source == null) { return "mv: cannot stat '" + sourcePath + "': No such file or directory"; }
+
+ TerminalDirectory destParent = CommandHelper.getParentDirectory(state, destPath);
+ if (destParent == null) { return "mv: cannot move '" + sourcePath + "' to '" + destPath + "': No such file or directory"; }
+
+ String destName = CommandHelper.getSimpleName(destPath);
+ if (destParent.hasChild(destName)) {
+ TerminalNode existing = destParent.getChild(destName);
+ if (existing.isDirectory()) { return "mv: cannot overwrite directory '" + destPath + "' with '" + sourcePath + "'"; }
+ if (source.isFile()) { ((TerminalFile) existing).setContent(((TerminalFile) source).getContent()); }
+ source.delete();
+ } else {
+ source.setName(destName);
+ source.getParent().removeChild(source);
+ destParent.addChild(source);
+ }
+ return "";
+ }
+
+ @Override
+ public String getName() { return "mv"; }
+ @Override
+ public String getDescription() { return "Move or rename files"; }
+ @Override
+
+ public String getUsage() { return "mv [OPTION] SOURCE DEST\n mv [OPTION] SOURCE... DIRECTORY"; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/PwdCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/PwdCommand.java
new file mode 100644
index 0000000..9a6ac27
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/PwdCommand.java
@@ -0,0 +1,21 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+
+/**
+ * pwd command - Print working directory
+ * @author Gabriel Chandesris (2026)
+ */
+public class PwdCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ return state.getCurrentDirectory().getPath();
+ }
+
+ @Override
+ public String getName() { return "pwd"; }
+ @Override
+ public String getDescription() { return "Print the current working directory"; }
+ @Override
+ public String getUsage() { return "pwd"; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/RmCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/RmCommand.java
new file mode 100644
index 0000000..b3729ce
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/RmCommand.java
@@ -0,0 +1,39 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalNode;
+
+/**
+ * rm command - Remove files
+ * @author Gabriel Chandesris (2026)
+ */
+public class RmCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0) { return "rm: missing operand"; }
+
+ StringBuilder output = new StringBuilder();
+ for (String fileName : args) {
+ TerminalNode node = CommandHelper.resolveFile(state, fileName);
+ if (node == null) {
+ output.append("rm: cannot remove '").append(fileName).append("': No such file or directory\n");
+ continue;
+ }
+ if (node.isDirectory()) {
+ output.append("rm: cannot remove '").append(fileName).append("': Is a directory\n");
+ continue;
+ }
+ if (!node.delete()) {
+ output.append("rm: cannot remove '").append(fileName).append("': Operation not permitted\n");
+ }
+ }
+ return output.toString();
+ }
+
+ @Override
+ public String getName() { return "rm"; }
+ @Override
+ public String getDescription() { return "Remove files"; }
+ @Override
+ public String getUsage() { return "rm FILE..."; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/RmdirCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/RmdirCommand.java
new file mode 100644
index 0000000..466b320
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/RmdirCommand.java
@@ -0,0 +1,45 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalDirectory;
+import gabywald.terminal.filesystem.TerminalNode;
+
+/**
+ * rmdir command - Remove empty directories
+ * @author Gabriel Chandesris (2026)
+ */
+public class RmdirCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0) { return "rmdir: missing operand"; }
+
+ StringBuilder output = new StringBuilder();
+ for (String dirName : args) {
+ TerminalNode node = CommandHelper.resolveDirectory(state, dirName);
+ if (node == null) {
+ output.append("rmdir: failed to remove '").append(dirName).append("': No such file or directory\n");
+ continue;
+ }
+ if (!node.isDirectory()) {
+ output.append("rmdir: failed to remove '").append(dirName).append("': Not a directory\n");
+ continue;
+ }
+ TerminalDirectory dir = (TerminalDirectory) node;
+ if (!dir.isEmpty()) {
+ output.append("rmdir: failed to remove '").append(dirName).append("': Directory not empty\n");
+ continue;
+ }
+ if (!dir.delete()) {
+ output.append("rmdir: failed to remove '").append(dirName).append("': Operation not permitted\n");
+ }
+ }
+ return output.toString();
+ }
+
+ @Override
+ public String getName() { return "rmdir"; }
+ @Override
+ public String getDescription() { return "Remove empty directories"; }
+ @Override
+ public String getUsage() { return "rmdir DIRECTORY..."; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/TouchCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/TouchCommand.java
new file mode 100644
index 0000000..e710c05
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/commands/TouchCommand.java
@@ -0,0 +1,61 @@
+package gabywald.terminal.commands;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.filesystem.TerminalDirectory;
+import gabywald.terminal.filesystem.TerminalNode;
+import gabywald.terminal.filesystem.TerminalFile;
+
+/**
+ * touch command - Create empty files or update timestamp
+ * @author Gabriel Chandesris (2026)
+ */
+public class TouchCommand implements Command {
+ @Override
+ public String execute(TerminalState state, String[] args) {
+ if (args.length == 0) { return "touch: missing file operand"; }
+
+ StringBuilder output = new StringBuilder();
+ for (String fileName : args) {
+ TerminalNode existing = resolveFile(state, fileName);
+ if (existing != null && existing.isDirectory()) {
+ output.append("touch: cannot touch '").append(fileName).append("': Is a directory\n");
+ continue;
+ }
+
+ TerminalDirectory parentDir = getParentDirectory(state, fileName);
+ if (parentDir == null) {
+ output.append("touch: cannot touch '").append(fileName).append("': No such file or directory\n");
+ continue;
+ }
+
+ String simpleName = getSimpleName(fileName);
+ if (existing != null && existing.isFile())
+ { ((TerminalFile) existing).setContent(((TerminalFile)existing).getContent()); }
+ else { parentDir.createFile(simpleName); }
+ }
+ return output.toString();
+ }
+
+ private TerminalNode resolveFile(TerminalState state, String fileName) {
+ TerminalDirectory current = state.getCurrentDirectory();
+ if (fileName.startsWith("/")) { return CommandHelper.resolveAbsolutePath(state.getRootDirectory(), fileName); }
+ return CommandHelper.resolveRelativePath(current, fileName);
+ }
+
+ private TerminalDirectory getParentDirectory(TerminalState state, String path) {
+ if (path.startsWith("/")) { return CommandHelper.getParentFromAbsolutePath(state.getRootDirectory(), path); }
+ return CommandHelper.getParentFromRelativePath(state.getCurrentDirectory(), path);
+ }
+
+ private String getSimpleName(String path) {
+ String[] parts = path.split("/");
+ return parts[parts.length - 1];
+ }
+
+ @Override
+ public String getName() { return "touch"; }
+ @Override
+ public String getDescription() { return "Create empty files or update timestamp"; }
+ @Override
+ public String getUsage() { return "touch FILE..."; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/FileSystem.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/FileSystem.java
new file mode 100644
index 0000000..3a2bf9f
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/FileSystem.java
@@ -0,0 +1,466 @@
+package gabywald.terminal.filesystem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Classe représentant un système de fichiers simplifié pour l'émulateur de terminal.
+ * Gère les fichiers et répertoires sous forme de structure arborescente.
+ *
+ *
+ * Fonctionnalités principales :
+ *
+ * - Création et suppression de fichiers et répertoires.
+ * - Navigation entre répertoires (cd, pwd).
+ * - Lecture et écriture de fichiers (cat, echo).
+ * - Vérification de l'existence de fichiers/répertoires.
+ * - Gestion des chemins absolus et relatifs.
+ *
+ *
+ *
+ *
+ * Exemple d'utilisation :
+ *
+ * FileSystem fs = new FileSystem();
+ * fs.mkdir("test"); // Crée un répertoire "test"
+ * fs.touch("file.txt"); // Crée un fichier "file.txt"
+ * fs.echo("file.txt", "Hello"); // Écrit "Hello" dans "file.txt"
+ * String content = fs.cat("file.txt"); // Lit le contenu
+ *
+ *
+ */
+public class FileSystem {
+ // Répertoire racine (constante)
+ private static final String ROOT = "/";
+
+ // Répertoire courant
+ private String currentDirectory;
+
+ // Structure des répertoires : clé = chemin absolu, valeur = ensemble des noms de fichiers/répertoires
+ private Map> directories;
+
+ // Structure des fichiers : clé = chemin absolu, valeur = contenu du fichier
+ private Map files;
+
+ /**
+ * Constructeur : initialise le système de fichiers avec un répertoire racine.
+ * Le répertoire racine est créé automatiquement.
+ */
+ public FileSystem() {
+ this.currentDirectory = FileSystem.ROOT;
+ this.directories = new HashMap<>();
+ this.files = new HashMap<>();
+
+ // Initialiser le répertoire racine
+ this.directories.put(ROOT, new HashSet<>());
+ }
+
+ // ============================================
+ // Méthodes de base pour le système de fichiers
+ // ============================================
+
+ /**
+ * Retourne le chemin du répertoire racine.
+ * @return Chemin absolu du répertoire racine ("/").
+ */
+ public String getRoot() { return FileSystem.ROOT; }
+
+ /**
+ * Retourne le répertoire courant.
+ * @return Chemin absolu du répertoire courant.
+ */
+ public String getCurrentDirectory() { return this.currentDirectory; }
+
+ /**
+ * Change le répertoire courant.
+ * @param path Chemin absolu du nouveau répertoire courant.
+ * @throws IllegalArgumentException Si le répertoire n'existe pas.
+ */
+ public void setCurrentDirectory(String path) {
+ if (!this.directories.containsKey(path)) {
+ throw new IllegalArgumentException("Répertoire introuvable: " + path);
+ }
+ this.currentDirectory = path;
+ }
+
+ // ============================================
+ // Méthodes pour les répertoires
+ // ============================================
+
+ /**
+ * Crée un nouveau répertoire dans le répertoire courant.
+ * @param dirName Nom du répertoire à créer.
+ * @return true si le répertoire a été créé, false s'il existe déjà.
+ * @throws IllegalArgumentException Si dirName est vide ou null.
+ */
+ public boolean mkdir(String dirName) {
+ if (dirName == null || dirName.trim().isEmpty()) {
+ throw new IllegalArgumentException("Nom de répertoire invalide.");
+ }
+
+ String fullPath = getFullPath(dirName);
+
+ // Vérifier si le répertoire existe déjà
+ if (this.directories.containsKey(fullPath)) {
+ return false;
+ }
+
+ // Créer le répertoire
+ this.directories.put(fullPath, new HashSet<>());
+
+ // Ajouter le répertoire au répertoire parent
+ this.directories.get(this.currentDirectory).add(dirName);
+
+ return true;
+ }
+
+ /**
+ * Supprime un répertoire ou un fichier.
+ * @param name Nom du répertoire ou fichier à supprimer.
+ * @return true si la suppression a réussi, false sinon.
+ */
+ public boolean rm(String name) {
+ if (name == null || name.trim().isEmpty()) {
+ return false;
+ }
+
+ String fullPath = getFullPath(name);
+
+ // Supprimer un fichier
+ if (this.files.containsKey(fullPath)) {
+ this.files.remove(fullPath);
+ this.directories.get(this.currentDirectory).remove(name);
+ return true;
+ }
+ // Supprimer un répertoire (doit être vide)
+ else if (this.directories.containsKey(fullPath)) {
+ Set contents = this.directories.get(fullPath);
+ if (contents.isEmpty()) {
+ this.directories.remove(fullPath);
+ this.directories.get(this.currentDirectory).remove(name);
+ return true;
+ } else {
+ return false; // Répertoire non vide
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Supprime un répertoire et tout son contenu (récursif).
+ * @param name Nom du répertoire à supprimer.
+ * @return true si la suppression a réussi, false sinon.
+ */
+ public boolean rmdir(String name) {
+ if (name == null || name.trim().isEmpty()) {
+ return false;
+ }
+
+ String fullPath = getFullPath(name);
+
+ if (!this.directories.containsKey(fullPath)) {
+ return false;
+ }
+
+ // Supprimer récursivement le contenu
+ Set contents = new HashSet<>(this.directories.get(fullPath));
+ for (String item : contents) {
+ String itemPath = fullPath.equals(ROOT) ? ROOT + item : fullPath + "/" + item;
+ if (this.directories.containsKey(itemPath)) {
+ rmdir(item); // Supprimer récursivement
+ } else if (this.files.containsKey(itemPath)) {
+ this.files.remove(itemPath);
+ }
+ }
+
+ // Supprimer le répertoire lui-même
+ this.directories.remove(fullPath);
+ this.directories.get(this.currentDirectory).remove(name);
+
+ return true;
+ }
+
+ /**
+ * Liste le contenu du répertoire courant.
+ * @return Liste des noms de fichiers et répertoires (les répertoires se terminent par "/").
+ */
+ public List ls() {
+ List contents = new ArrayList();
+ Set dirContents = this.directories.getOrDefault(this.currentDirectory, new HashSet<>());
+
+ for (String item : dirContents) {
+ String fullPath = getFullPath(item);
+ if (this.directories.containsKey(fullPath)) {
+ contents.add(item + "/"); // Ajouter "/" pour les répertoires
+ } else if (this.files.containsKey(fullPath)) {
+ contents.add(item);
+ }
+ }
+
+ Collections.sort(contents); // Trier par ordre alphabétique
+ return contents;
+ }
+
+ /**
+ * Liste le contenu d'un répertoire spécifique.
+ * @param dirPath Chemin du répertoire à lister.
+ * @return Liste des noms de fichiers et répertoires.
+ * @throws IllegalArgumentException Si le répertoire n'existe pas.
+ */
+ public List ls(String dirPath) {
+ if (!this.directories.containsKey(dirPath)) {
+ throw new IllegalArgumentException("Répertoire introuvable: " + dirPath);
+ }
+
+ List contents = new ArrayList<>();
+ Set dirContents = this.directories.get(dirPath);
+
+ for (String item : dirContents) {
+ String fullPath = dirPath.equals(ROOT) ? ROOT + item : dirPath + "/" + item;
+ if (this.directories.containsKey(fullPath)) {
+ contents.add(item + "/");
+ } else if (this.files.containsKey(fullPath)) {
+ contents.add(item);
+ }
+ }
+
+ Collections.sort(contents);
+ return contents;
+ }
+
+ // ============================================
+ // Méthodes pour les fichiers
+ // ============================================
+
+ /**
+ * Crée un nouveau fichier vide.
+ * @param fileName Nom du fichier à créer.
+ * @return true si le fichier a été créé, false s'il existe déjà.
+ * @throws IllegalArgumentException Si fileName est vide ou null.
+ */
+ public boolean touch(String fileName) {
+ if (fileName == null || fileName.trim().isEmpty()) {
+ throw new IllegalArgumentException("Nom de fichier invalide.");
+ }
+
+ String fullPath = getFullPath(fileName);
+
+ if (this.files.containsKey(fullPath)) {
+ return false; // Fichier déjà existant
+ }
+
+ this.files.put(fullPath, "");
+ return true;
+ }
+
+ /**
+ * Lit le contenu d'un fichier.
+ * @param fileName Nom du fichier à lire.
+ * @return Contenu du fichier, ou un message d'erreur si le fichier n'existe pas.
+ */
+ public String cat(String fileName) {
+ String fullPath = getFullPath(fileName);
+ return this.files.getOrDefault(fullPath, "Fichier introuvable: " + fileName);
+ }
+
+ /**
+ * Écrit du contenu dans un fichier (écrasement).
+ * @param fileName Nom du fichier.
+ * @param content Contenu à écrire.
+ * @return true si l'écriture a réussi, false sinon.
+ */
+ public boolean echo(String fileName, String content) {
+ if (fileName == null || fileName.trim().isEmpty()) {
+ return false;
+ }
+
+ String fullPath = getFullPath(fileName);
+ this.files.put(fullPath, content);
+ return true;
+ }
+
+ /**
+ * Ajoute du contenu à la fin d'un fichier.
+ * @param fileName Nom du fichier.
+ * @param content Contenu à ajouter.
+ * @return true si l'ajout a réussi, false sinon.
+ */
+ public boolean append(String fileName, String content) {
+ if (fileName == null || fileName.trim().isEmpty()) {
+ return false;
+ }
+
+ String fullPath = getFullPath(fileName);
+ String existingContent = this.files.getOrDefault(fullPath, "");
+ this.files.put(fullPath, existingContent + content);
+ return true;
+ }
+
+ // ============================================
+ // Méthodes utilitaires pour les chemins
+ // ============================================
+
+ /**
+ * Construit le chemin absolu à partir d'un nom de fichier/répertoire.
+ * @param name Nom du fichier ou répertoire.
+ * @return Chemin absolu.
+ */
+ private String getFullPath(String name) {
+ if (this.currentDirectory.equals(ROOT)) {
+ return ROOT + name;
+ }
+ return this.currentDirectory + "/" + name;
+ }
+
+ /**
+ * Vérifie si un fichier ou répertoire existe.
+ * @param name Nom du fichier ou répertoire.
+ * @return true s'il existe, false sinon.
+ */
+ public boolean exists(String name) {
+ String fullPath = getFullPath(name);
+ return this.files.containsKey(fullPath) || this.directories.containsKey(fullPath);
+ }
+
+ /**
+ * Retourne le chemin absolu du répertoire parent.
+ * @param path Chemin dont on veut le parent.
+ * @return Chemin absolu du répertoire parent.
+ */
+ public String getParentDirectory(String path) {
+ if (path.equals(ROOT)) {
+ return ROOT;
+ }
+
+ String[] parts = path.split("/");
+ if (parts.length <= 2) {
+ return ROOT;
+ }
+
+ StringBuilder parentPath = new StringBuilder();
+ for (int i = 1; i < parts.length - 1; i++) {
+ parentPath.append("/").append(parts[i]);
+ }
+
+ return parentPath.length() == 0 ? ROOT : parentPath.toString();
+ }
+
+ /**
+ * Résout un chemin (relatif ou absolu) en chemin absolu.
+ * @param path Chemin à résoudre (peut être relatif ou absolu).
+ * @return Chemin absolu résolu.
+ * @throws IllegalArgumentException Si le chemin est invalide.
+ */
+ public String resolvePath(String path) {
+ if (path == null || path.trim().isEmpty()) {
+ return this.currentDirectory;
+ }
+
+ path = path.trim();
+
+ // Si le chemin est absolu (commence par "/")
+ if (path.startsWith(ROOT)) {
+ return normalizePath(path);
+ }
+
+ // Sinon, c'est un chemin relatif : le résoudre par rapport au répertoire courant
+ String resolvedPath = this.currentDirectory + "/" + path;
+ return normalizePath(resolvedPath);
+ }
+
+ /**
+ * Normalise un chemin (supprime les "/./" et résout les "/../").
+ * @param path Chemin à normaliser.
+ * @return Chemin normalisé.
+ */
+ public String normalizePath(String path) {
+ if (path.equals(ROOT)) {
+ return ROOT;
+ }
+
+ String[] parts = path.split("/");
+ List normalizedParts = new ArrayList<>();
+
+ for (String part : parts) {
+ if (part.isEmpty() || part.equals(".")) {
+ continue; // Ignorer les parties vides ou "."
+ } else if (part.equals("..")) {
+ if (!normalizedParts.isEmpty()) {
+ normalizedParts.remove(normalizedParts.size() - 1); // Remonter d'un niveau
+ }
+ } else {
+ normalizedParts.add(part);
+ }
+ }
+
+ if (normalizedParts.isEmpty()) {
+ return ROOT;
+ }
+
+ StringBuilder normalizedPath = new StringBuilder();
+ for (String part : normalizedParts) {
+ normalizedPath.append("/").append(part);
+ }
+
+ return normalizedPath.toString();
+ }
+
+ // ============================================
+ // Méthodes pour la gestion des permissions (optionnel)
+ // ============================================
+
+ /**
+ * Vérifie si un chemin est un répertoire.
+ * @param path Chemin à vérifier.
+ * @return true si c'est un répertoire, false sinon.
+ */
+ public boolean isDirectory(String path) {
+ return this.directories.containsKey(path);
+ }
+
+ /**
+ * Vérifie si un chemin est un fichier.
+ * @param path Chemin à vérifier.
+ * @return true si c'est un fichier, false sinon.
+ */
+ public boolean isFile(String path) {
+ return this.files.containsKey(path);
+ }
+
+ // ============================================
+ // Méthodes pour la sérialisation (optionnel)
+ // ============================================
+
+ /**
+ * Exporte l'état du système de fichiers sous forme de chaîne de caractères.
+ * Utile pour sauvegarder/restaurer l'état.
+ * @return Représentation textuelle du système de fichiers.
+ */
+ public String exportState() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("=== Directories ===\\n");
+ for (Map.Entry> entry : this.directories.entrySet()) {
+ sb.append(entry.getKey()).append(": ").append(entry.getValue()).append("\\n");
+ }
+ sb.append("\\n=== Files ===\\n");
+ for (Map.Entry entry : this.files.entrySet()) {
+ sb.append(entry.getKey()).append(": \"").append(entry.getValue()).append("\"\\n");
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Importe l'état du système de fichiers à partir d'une chaîne de caractères.
+ * @param state Représentation textuelle du système de fichiers.
+ */
+ public void importState(String state) {
+ // TODO: Implémenter la désérialisation
+ throw new UnsupportedOperationException("Non implémenté.");
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalDirectory.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalDirectory.java
new file mode 100644
index 0000000..a161097
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalDirectory.java
@@ -0,0 +1,115 @@
+package gabywald.terminal.filesystem;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Directory class representing a folder in the file system.
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalDirectory extends TerminalNode {
+ private List children;
+
+ public TerminalDirectory(String name, TerminalDirectory parent) {
+ super(name, parent);
+ this.children = new ArrayList();
+ }
+
+ public List getChildren() { return Collections.unmodifiableList(children); }
+
+ public boolean addChild(TerminalNode child) {
+ if (child == null) { return false; }
+ for (TerminalNode existing : children) {
+ if (existing.getName().equals(child.getName()))
+ { return false; }
+ }
+ child.setParent(this);
+ this.children.add(child);
+ this.modifiedAt = java.time.LocalDateTime.now();
+ return true;
+ }
+
+ public boolean removeChild(TerminalNode child) {
+ if (child == null) { return false; }
+ boolean removed = this.children.remove(child);
+ if (removed) {
+ child.setParent(null);
+ this.modifiedAt = java.time.LocalDateTime.now();
+ }
+ return removed;
+ }
+
+ public boolean removeChildByName(String name) {
+ TerminalNode toRemove = getChild(name);
+ if (toRemove != null) { return removeChild(toRemove); }
+ return false;
+ }
+
+ public TerminalNode getChild(String name) {
+ for (TerminalNode child : children) {
+ if (child.getName().equals(name))
+ { return child; }
+ }
+ return null;
+ }
+
+ public boolean hasChild(String name) { return getChild(name) != null; }
+
+ public List getSubdirectories() {
+ return this.children.stream()
+ .filter(TerminalNode::isDirectory)
+ .map(node -> (TerminalDirectory) node)
+ .collect(Collectors.toList());
+ }
+
+ public List getFiles() {
+ return this.children.stream()
+ .filter(TerminalNode::isFile)
+ .map(node -> (TerminalFile) node)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ public String getPath() {
+ if (this.parent == null) { return "/"; }
+ String parentPath = parent.getPath();
+ if ("/".equals(parentPath)) return "/" + this.name;
+ return parentPath + "/" + this.name;
+ }
+
+ @Override
+ public boolean isDirectory() { return true; }
+ @Override
+ public boolean isFile() { return false; }
+ @Override
+ public long getSize() { return 0; }
+
+ @Override
+ public boolean delete() {
+ if (this.parent != null) return this.parent.removeChild(this);
+ return false;
+ }
+
+ public void clear() { this.children.clear(); this.modifiedAt = java.time.LocalDateTime.now(); }
+ public int getChildCount() { return this.children.size(); }
+ public boolean isEmpty() { return this.children.isEmpty(); }
+
+ public TerminalDirectory createDirectory(String name) {
+ if (this.hasChild(name)) { return null; }
+ TerminalDirectory dir = new TerminalDirectory(name, this);
+ this.addChild(dir);
+ return dir;
+ }
+
+ public TerminalFile createFile(String name) {
+ if (this.hasChild(name)) { return null; }
+ TerminalFile file = new TerminalFile(name, this);
+ this.addChild(file);
+ return file;
+ }
+
+ @Override
+ public String toString() { return "[DIR] " + this.name + " (" + this.children.size() + " items)"; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalFile.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalFile.java
new file mode 100644
index 0000000..e70a299
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalFile.java
@@ -0,0 +1,60 @@
+package gabywald.terminal.filesystem;
+
+/**
+ * TerminalFile class representing a text file in the file system.
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalFile extends TerminalNode {
+ private String content;
+
+ public TerminalFile(String name, TerminalDirectory parent) {
+ super(name, parent);
+ this.content = "";
+ }
+
+ public TerminalFile(String name, TerminalDirectory parent, String content) {
+ super(name, parent);
+ this.content = ((content != null) ? content : "");
+ }
+
+ public String getContent() { return content; }
+
+ public void setContent(String content) {
+ this.content = content != null ? content : "";
+ this.modifiedAt = java.time.LocalDateTime.now();
+ }
+
+ public void appendContent(String text) {
+ if (text != null) {
+ this.content += text;
+ this.modifiedAt = java.time.LocalDateTime.now();
+ }
+ }
+
+ @Override
+ public String getPath() {
+ if (parent == null) { return "/" + name; }
+ String parentPath = parent.getPath();
+ if ("/".equals(parentPath)) { return "/" + name; }
+ return parentPath + "/" + name;
+ }
+
+ @Override
+ public boolean isDirectory() { return false; }
+ @Override
+ public boolean isFile() { return true; }
+ @Override
+ public long getSize() { return content.getBytes().length; }
+
+ @Override
+ public boolean delete() {
+ if (parent != null) { return parent.removeChild(this); }
+ return false;
+ }
+
+ public void clear() { this.content = ""; this.modifiedAt = java.time.LocalDateTime.now(); }
+ public boolean isEmpty() { return content.isEmpty(); }
+
+ @Override
+ public String toString() { return "[FILE] " + name + " (" + getSize() + " bytes)"; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalNode.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalNode.java
new file mode 100644
index 0000000..423b1e4
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/filesystem/TerminalNode.java
@@ -0,0 +1,58 @@
+package gabywald.terminal.filesystem;
+
+import java.time.LocalDateTime;
+
+/**
+ * Abstract base class for file system nodes (files and directories).
+ * @author Gabriel Chandesris (2026)
+ */
+public abstract class TerminalNode implements Comparable {
+
+ protected String name;
+ protected TerminalDirectory parent;
+ protected LocalDateTime createdAt;
+ protected LocalDateTime modifiedAt;
+
+ public TerminalNode(String name, TerminalDirectory parent) {
+ this.name = name;
+ this.parent = parent;
+ this.createdAt = LocalDateTime.now();
+ this.modifiedAt = LocalDateTime.now();
+ }
+
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; this.modifiedAt = LocalDateTime.now(); }
+ public TerminalDirectory getParent() { return parent; }
+ public void setParent(TerminalDirectory parent) { this.parent = parent; this.modifiedAt = LocalDateTime.now(); }
+ public LocalDateTime getCreatedAt() { return createdAt; }
+ public LocalDateTime getModifiedAt() { return modifiedAt; }
+
+ public abstract String getPath();
+ public abstract boolean isDirectory();
+ public abstract boolean isFile();
+ public abstract long getSize();
+ public abstract boolean delete();
+
+ @Override
+ public String toString() { return name; }
+
+ @Override
+ public int compareTo(TerminalNode other) { return this.name.compareTo(other.name); }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) { return true; }
+ if (obj == null || this.getClass() != obj.getClass()) { return false;}
+ TerminalNode fileNode = (TerminalNode) obj;
+ return name.equals(fileNode.name) &&
+ ( (parent == null) ? fileNode.parent == null : parent.equals(fileNode.parent));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name.hashCode();
+ result = 31 * result + ( (parent != null) ? parent.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/CommandActionListener.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/CommandActionListener.java
new file mode 100644
index 0000000..e28cad2
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/CommandActionListener.java
@@ -0,0 +1,60 @@
+package gabywald.terminal.gui;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+import gabywald.terminal.commands.Command;
+import gabywald.terminal.commands.CommandFactory;
+import gabywald.terminal.commands.CommandParser;
+
+/**
+ *
+ * @author Gabriel Chandesris (2026)
+ */
+public class CommandActionListener implements ActionListener {
+ private TerminalFrame localtf = null;
+
+ CommandActionListener(TerminalFrame tf)
+ { this.localtf = tf; }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String command = this.localtf.getInputField().getText().trim();
+ if (command.isEmpty()) {
+ this.localtf.clearInputLine();
+ return;
+ }
+
+ this.localtf.getTerminalState().addToHistory(command);
+ this.localtf.getTerminalState().resetHistoryIndex();
+ this.localtf.appendOutput(this.localtf.getTerminalState().getPrompt() + " " + command + "\n");
+
+ String[] parts = CommandParser.parse(command);
+ if (parts.length == 0) {
+ this.localtf.clearInputLine();
+ return;
+ }
+
+ String commandName = parts[0];
+ String[] cmdArgs = new String[parts.length - 1];
+ System.arraycopy(parts, 1, cmdArgs, 0, cmdArgs.length);
+
+ Command cmd = CommandFactory.getCommand(commandName);
+ if (cmd == null) {
+ this.localtf.appendOutput(commandName + ": command not found\n");
+ } else {
+ String result = cmd.execute(this.localtf.getTerminalState(), cmdArgs);
+ if ("EXIT".equals(result)) {
+ System.exit(0);
+ } else if (result.contains("\033[H\033[2J")) {
+ this.localtf.clearScreen();
+ this.localtf.printWelcomeMessage();
+ } else {
+ this.localtf.appendOutput(result + "\n");
+ }
+ }
+
+ this.localtf.updatePrompt();
+ this.localtf.clearInputLine();
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/HistoryKeyListener.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/HistoryKeyListener.java
new file mode 100644
index 0000000..50d3b77
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/HistoryKeyListener.java
@@ -0,0 +1,52 @@
+package gabywald.terminal.gui;
+
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+
+import gabywald.terminal.commands.CommandFactory;
+import gabywald.terminal.commands.CommandParser;
+
+/**
+ *
+ * @author Gabriel Chandesris (2026)
+ */
+public class HistoryKeyListener extends KeyAdapter {
+
+ private TerminalFrame localtf = null;
+
+ HistoryKeyListener(TerminalFrame tf)
+ { this.localtf = tf; }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ if (e.getKeyCode() == KeyEvent.VK_UP) {
+ String prevCommand = this.localtf.getTerminalState().getPreviousCommand();
+ if (prevCommand != null) { this.localtf.getInputField().setText(prevCommand); }
+ e.consume();
+ } else if (e.getKeyCode() == KeyEvent.VK_DOWN) {
+ String nextCommand = this.localtf.getTerminalState().getNextCommand();
+ if (nextCommand != null) { this.localtf.getInputField().setText(nextCommand); }
+ e.consume();
+ } else if (e.getKeyCode() == KeyEvent.VK_TAB) {
+ handleTabCompletion();
+ e.consume();
+ }
+ }
+
+ private void handleTabCompletion() {
+ String text = this.localtf.getInputField().getText();
+ String[] parts = CommandParser.parse(text);
+ if (parts.length == 0) { return; }
+
+ if (parts.length == 1) {
+ String prefix = parts[0];
+ String[] commandNames = CommandFactory.getCommandNames();
+ for (String cmdName : commandNames) {
+ if (cmdName.startsWith(prefix)) {
+ this.localtf.getInputField().setText(cmdName + " ");
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalFrame.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalFrame.java
new file mode 100644
index 0000000..127f9c4
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalFrame.java
@@ -0,0 +1,118 @@
+package gabywald.terminal.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+
+import javax.swing.BorderFactory;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.SwingConstants;
+
+import gabywald.terminal.TerminalState;
+
+
+/**
+ * Main terminal window with Swing GUI
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalFrame extends JFrame {
+
+ private static final long serialVersionUID = 1L;
+
+ private TerminalState state;
+ private JTextArea outputArea;
+ private JTextField inputField;
+ private JScrollPane scrollPane;
+ private JLabel promptLabel;
+
+ public TerminalFrame() {
+ this(new TerminalState());
+ }
+
+ public TerminalFrame(TerminalState state) {
+ this.state = state;
+ this.initializeUI();
+ }
+
+ private void initializeUI() {
+ this.setTitle("Terminal Emulator");
+ this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.setSize(800, 600);
+ this.setLocationRelativeTo(null);
+
+ JPanel mainPanel = new JPanel(new BorderLayout());
+ mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+ this.outputArea = new JTextArea();
+ this.outputArea.setEditable(false);
+ this.outputArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
+ this.outputArea.setBackground(Color.BLACK);
+ this.outputArea.setForeground(Color.WHITE);
+ this.outputArea.setCaretColor(Color.WHITE);
+
+ this.scrollPane = new JScrollPane(this.outputArea);
+ this.scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+ this.scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+
+ mainPanel.add(this.scrollPane, BorderLayout.CENTER);
+
+ JPanel inputPanel = new JPanel(new BorderLayout());
+ inputPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0));
+
+ this.promptLabel = new JLabel(this.state.getPrompt() + " ", SwingConstants.RIGHT);
+ this.promptLabel.setFont(new Font("Monospaced", Font.PLAIN, 14));
+ this.promptLabel.setForeground(Color.WHITE);
+ this.promptLabel.setBackground(Color.BLACK);
+ this.promptLabel.setOpaque(true);
+
+ this.inputField = new JTextField();
+ this.inputField.setFont(new Font("Monospaced", Font.PLAIN, 14));
+ this.inputField.setBackground(Color.BLACK);
+ this.inputField.setForeground(Color.WHITE);
+ this.inputField.setCaretColor(Color.WHITE);
+ this.inputField.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
+
+ this.inputField.addActionListener(new CommandActionListener(this));
+ this.inputField.addKeyListener(new HistoryKeyListener(this));
+
+ inputPanel.add(this.promptLabel, BorderLayout.WEST);
+ inputPanel.add(this.inputField, BorderLayout.CENTER);
+
+ mainPanel.add(inputPanel, BorderLayout.SOUTH);
+
+ this.add(mainPanel);
+
+ this.printWelcomeMessage();
+ this.inputField.requestFocusInWindow();
+ }
+
+ void printWelcomeMessage() {
+ this.appendOutput("===============================================\n");
+ this.appendOutput(" TERMINAL EMULATOR - Java 8 / Swing\n");
+ this.appendOutput(" Type 'help' for a list of available commands\n");
+ this.appendOutput(" Type 'exit' to quit\n");
+ this.appendOutput("===============================================\n\n");
+ }
+
+ void appendOutput(String text) {
+ this.outputArea.append(text);
+ this.outputArea.setCaretPosition(outputArea.getDocument().getLength());
+ }
+
+ void clearScreen() { this.outputArea.setText(""); }
+
+ void clearInputLine() { this.inputField.setText(""); }
+
+ void updatePrompt() { this.promptLabel.setText(this.state.getPrompt() + " "); }
+
+ JTextField getInputField() { return this.inputField; }
+
+ public TerminalState getTerminalState() { return this.state; }
+ public void setState(TerminalState state) { this.state = state;this.updatePrompt(); }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalOutput.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalOutput.java
new file mode 100644
index 0000000..5ae3979
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalOutput.java
@@ -0,0 +1,80 @@
+package gabywald.terminal.gui;
+
+import java.awt.Color;
+import java.awt.Font;
+
+import javax.swing.JTextPane;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.DefaultStyledDocument;
+import javax.swing.text.Document;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+
+/**
+ * Terminal output with color support
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalOutput {
+
+ private JTextPane textPane;
+ private StyleContext styleContext;
+ private Style defaultStyle;
+ private Style errorStyle;
+ private Style successStyle;
+ private Style promptStyle;
+ private Style commandStyle;
+
+ public TerminalOutput() {
+ this.textPane = new JTextPane();
+ this.textPane.setEditable(false);
+ this.textPane.setFont(new Font("Monospaced", Font.PLAIN, 14));
+ this.textPane.setBackground(Color.BLACK);
+ this.textPane.setCaretColor(Color.WHITE);
+
+ this.styleContext = new StyleContext();
+
+ this.defaultStyle = this.styleContext.addStyle("default", null);
+ StyleConstants.setForeground(this.defaultStyle, Color.WHITE);
+
+ this.errorStyle = this.styleContext.addStyle("error", null);
+ StyleConstants.setForeground(this.errorStyle, Color.RED);
+
+ this.successStyle = this.styleContext.addStyle("success", null);
+ StyleConstants.setForeground(this.successStyle, Color.GREEN);
+
+ this.promptStyle = this.styleContext.addStyle("prompt", null);
+ StyleConstants.setForeground(promptStyle, Color.CYAN);
+
+ commandStyle = styleContext.addStyle("command", null);
+ StyleConstants.setForeground(this.commandStyle, Color.YELLOW);
+
+ this.textPane.setDocument(new DefaultStyledDocument(styleContext));
+ }
+
+ public void append(String text) { this.append(text, this.defaultStyle); }
+
+ public void append(String text, Style style) {
+ try {
+ Document doc = this.textPane.getDocument();
+ doc.insertString(doc.getLength(), text, style);
+ this.textPane.setCaretPosition(doc.getLength());
+ } catch (BadLocationException e) { e.printStackTrace(); }
+ }
+
+ public void appendLine(String text) { this.append(text + "\n"); }
+ public void appendLine(String text, Style style) { this.append(text + "\n", style); }
+ public void appendPrompt(String prompt) { this.append(prompt + " ", this.promptStyle); }
+ public void appendCommand(String command) { this.append(command, this.commandStyle); }
+ public void appendError(String error) { this.appendLine(error, this.errorStyle); }
+ public void appendSuccess(String message) { this.appendLine(message, this.successStyle); }
+ public void clearScreen() { this.textPane.setText(""); }
+
+ public JTextPane getTextPane() { return this.textPane; }
+ public Style getDefaultStyle() { return this.defaultStyle; }
+ public Style getErrorStyle() { return this.errorStyle; }
+ public Style getSuccessStyle() { return this.successStyle; }
+ public Style getPromptStyle() { return this.promptStyle; }
+ public Style getCommandStyle() { return this.commandStyle; }
+
+}
\ No newline at end of file
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalPanel.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalPanel.java
new file mode 100644
index 0000000..0956e88
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/gui/TerminalPanel.java
@@ -0,0 +1,51 @@
+package gabywald.terminal.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+
+/**
+ * Terminal panel with output display
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalPanel extends JPanel {
+ private static final long serialVersionUID = 1L;
+ private JTextArea outputArea;
+ private JScrollPane scrollPane;
+
+ public TerminalPanel() {
+ setLayout(new BorderLayout());
+ setBackground(Color.BLACK);
+
+ this.outputArea = new JTextArea();
+ this.outputArea.setEditable(false);
+ this.outputArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
+ this.outputArea.setBackground(Color.BLACK);
+ this.outputArea.setForeground(Color.WHITE);
+ this.outputArea.setCaretColor(Color.WHITE);
+
+ this.scrollPane = new JScrollPane(this.outputArea);
+ this.scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
+ this.scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ this.scrollPane.setBorder(BorderFactory.createEmptyBorder());
+
+ this.add(this.scrollPane, BorderLayout.CENTER);
+ }
+
+ public void appendOutput(String text) {
+ this.outputArea.append(text);
+ this.outputArea.setCaretPosition(this.outputArea.getDocument().getLength());
+ }
+
+ public void clearScreen() { this.outputArea.setText(""); }
+ public void setOutputText(String text) { this.outputArea.setText(text); }
+ public String getOutputText() { return this.outputArea.getText(); }
+ public JTextArea getOutputArea() { return this.outputArea; }
+
+}
+
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptContext.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptContext.java
new file mode 100644
index 0000000..7e80d44
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptContext.java
@@ -0,0 +1,96 @@
+package gabywald.terminal.script;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Script execution context with variables and control flow state
+ * @author Gabriel Chandesris (2026)
+ */
+public class ScriptContext {
+ private Map variables;
+ private boolean inIfBlock;
+ private boolean ifConditionResult;
+ private int ifBlockDepth;
+ private boolean inWhileBlock;
+ private String whileCondition;
+ private boolean whileConditionResult;
+ private int whileBlockDepth;
+ private boolean inForBlock;
+ private String forVariable;
+ private String[] forValues;
+ private int forIndex;
+ private int forBlockDepth;
+
+ public ScriptContext()
+ { this.variables = new HashMap(); }
+
+ public Map getVariables() { return this.variables; }
+ public void setVariables(Map variables)
+ { this.variables = variables; }
+
+ public boolean isInIfBlock() { return this.inIfBlock; }
+ public void setInIfBlock(boolean inIfBlock)
+ { this.inIfBlock = inIfBlock; }
+
+ public boolean isIfConditionResult() { return this.ifConditionResult; }
+ public void setIfConditionResult(boolean ifConditionResult)
+ { this.ifConditionResult = ifConditionResult; }
+
+ public int getIfBlockDepth() { return this.ifBlockDepth; }
+ public void setIfBlockDepth(int ifBlockDepth)
+ { this.ifBlockDepth = ifBlockDepth; }
+
+ public boolean isInWhileBlock() { return this.inWhileBlock; }
+ public void setInWhileBlock(boolean inWhileBlock)
+ { this.inWhileBlock = inWhileBlock; }
+
+ public String getWhileCondition() { return this.whileCondition; }
+ public void setWhileCondition(String whileCondition)
+ { this.whileCondition = whileCondition; }
+
+ public boolean isWhileConditionResult() { return this.whileConditionResult; }
+ public void setWhileConditionResult(boolean whileConditionResult)
+ { this.whileConditionResult = whileConditionResult; }
+
+ public int getWhileBlockDepth() { return this.whileBlockDepth; }
+ public void setWhileBlockDepth(int whileBlockDepth)
+ { this.whileBlockDepth = whileBlockDepth; }
+
+ public boolean isInForBlock() { return this.inForBlock; }
+ public void setInForBlock(boolean inForBlock)
+ { this.inForBlock = inForBlock; }
+
+ public String getForVariable() { return this.forVariable; }
+ public void setForVariable(String forVariable)
+ { this.forVariable = forVariable; }
+
+ public String[] getForValues() { return this.forValues; }
+ public void setForValues(String[] forValues)
+ { this.forValues = forValues; }
+
+ public int getForIndex() { return this.forIndex; }
+ public void setForIndex(int forIndex)
+ { this.forIndex = forIndex; }
+
+ public int getForBlockDepth() { return this.forBlockDepth; }
+ public void setForBlockDepth(int forBlockDepth)
+ { this.forBlockDepth = forBlockDepth; }
+
+ public void reset() {
+ this.variables.clear();
+ this.inIfBlock = false;
+ this.ifConditionResult = false;
+ this.ifBlockDepth = 0;
+ this.inWhileBlock = false;
+ this.whileCondition = "";
+ this.whileConditionResult = false;
+ this.whileBlockDepth = 0;
+ this.inForBlock = false;
+ this.forVariable = "";
+ this.forValues = new String[0];
+ this.forIndex = 0;
+ this.forBlockDepth = 0;
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptEngine.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptEngine.java
new file mode 100644
index 0000000..82cb410
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptEngine.java
@@ -0,0 +1,263 @@
+package gabywald.terminal.script;
+
+import gabywald.terminal.TerminalState;
+import gabywald.terminal.commands.Command;
+import gabywald.terminal.commands.CommandFactory;
+import gabywald.terminal.commands.CommandParser;
+import gabywald.terminal.filesystem.TerminalDirectory;
+import gabywald.terminal.filesystem.TerminalNode;
+import gabywald.terminal.filesystem.TerminalFile;
+import java.util.Map;
+
+/**
+ * Script execution engine with minimal scripting language support
+ * @author Gabriel Chandesris (2026)
+ */
+public class ScriptEngine {
+
+ private TerminalState state;
+ private ScriptContext context;
+
+ public ScriptEngine(TerminalState state) {
+ this.state = state;
+ this.context = new ScriptContext();
+ }
+
+ public String executeFile(TerminalFile file) {
+ if (file == null) { return "Script file not found"; }
+ return execute(file.getContent());
+ }
+
+ public String execute(String script) {
+ if (script == null || script.trim().isEmpty()) { return ""; }
+
+ StringBuilder output = new StringBuilder();
+ String[] lines = script.split("\n");
+
+ for (String line : lines) {
+ String trimmed = line.trim();
+ if (trimmed.startsWith("#") || trimmed.isEmpty()) { continue; }
+
+ String result = executeLine(trimmed);
+ if (result != null && !result.isEmpty())
+ { output.append(result).append("\n"); }
+ }
+ return output.toString();
+ }
+
+ private String executeLine(String line) {
+ if (line.startsWith("if ")) return executeIf(line);
+ if (line.startsWith("while ")) return executeWhile(line);
+ if (line.startsWith("for ")) return executeFor(line);
+ if (line.equals("fi") || line.equals("done") || line.equals("end")) return null;
+ if (line.startsWith("set ")) return executeSet(line);
+ if (line.startsWith("echo ")) return executeScriptEcho(line);
+ if (line.startsWith("cd ")) return executeScriptCd(line);
+ if (line.startsWith("pwd")) return state.getCurrentDirectory().getPath();
+ if (line.startsWith("ls")) return executeLs(line);
+ if (line.startsWith("cat")) return executeCat(line);
+
+ String[] parts = CommandParser.parse(line);
+ if (parts.length > 0) {
+ Command cmd = CommandFactory.getCommand(parts[0]);
+ if (cmd != null) {
+ String[] args = new String[parts.length - 1];
+ System.arraycopy(parts, 1, args, 0, args.length);
+ return cmd.execute(state, args);
+ }
+ }
+ return "Script error: unknown command '" + line.split(" ")[0] + "'";
+ }
+
+ private String executeIf(String line) {
+ String condition = line.substring(3, line.length() - 1).trim();
+ boolean result = evaluateCondition(condition);
+ context.setInIfBlock(true);
+ context.setIfConditionResult(result);
+ context.setIfBlockDepth(1);
+ return null;
+ }
+
+ private String executeWhile(String line) {
+ String condition = line.substring(6, line.length() - 1).trim();
+ boolean result = evaluateCondition(condition);
+ context.setInWhileBlock(true);
+ context.setWhileCondition(condition);
+ context.setWhileConditionResult(result);
+ context.setWhileBlockDepth(1);
+ return null;
+ }
+
+ private String executeFor(String line) {
+ String[] parts = line.substring(4).trim().split(" in ");
+ if (parts.length != 2) { return "Script error: invalid for syntax"; }
+
+ String varName = parts[0].trim();
+ String[] values = parts[1].trim().split(" ");
+
+ context.setInForBlock(true);
+ context.setForVariable(varName);
+ context.setForValues(values);
+ context.setForIndex(0);
+ context.setForBlockDepth(1);
+
+ if (values.length > 0) { context.getVariables().put(varName, values[0]); }
+ return null;
+ }
+
+ private String executeSet(String line) {
+ String[] parts = line.substring(4).trim().split("=", 2);
+ if (parts.length != 2) { return "Script error: invalid set syntax"; }
+
+ String varName = parts[0].trim();
+ String value = parts[1].trim();
+ value = replaceVariables(value);
+ context.getVariables().put(varName, value);
+ return null;
+ }
+
+ private String executeScriptEcho(String line) {
+ String text = line.substring(5).trim();
+ return replaceVariables(text);
+ }
+
+ private String executeScriptCd(String line) {
+ String path = line.substring(3).trim();
+ TerminalDirectory newDir = resolveDirectory(state, path);
+ if (newDir == null) { return "cd: no such file or directory: " + path; }
+ state.setCurrentDirectory(newDir);
+ return null;
+ }
+
+ private String executeLs(String line) {
+ String[] parts = CommandParser.parse(line);
+ String[] args = new String[parts.length - 1];
+ System.arraycopy(parts, 1, args, 0, args.length);
+ Command cmd = CommandFactory.getCommand("ls");
+ if (cmd != null) { return cmd.execute(state, args); }
+ return "ls: command not available";
+ }
+
+ private String executeCat(String line) {
+ String[] parts = CommandParser.parse(line);
+ String[] args = new String[parts.length - 1];
+ System.arraycopy(parts, 1, args, 0, args.length);
+ Command cmd = CommandFactory.getCommand("cat");
+ if (cmd != null) { return cmd.execute(state, args); }
+ return "cat: command not available";
+ }
+
+ private boolean evaluateCondition(String condition) {
+ condition = condition.trim();
+ if (condition.startsWith("$")) {
+ String[] parts = condition.split("=");
+ if (parts.length == 2) {
+ String varName = parts[0].trim();
+ String value = parts[1].trim().replaceAll("^$", ""); // NOTE "^\"|"$"
+ String varValue = context.getVariables().getOrDefault(varName, "");
+ return varValue.equals(value);
+ }
+ } else if (condition.startsWith("-")) {
+ String[] parts = condition.split(" ");
+ if (parts.length == 2) {
+ String test = parts[0];
+ String path = parts[1].trim().replaceAll("^$", ""); // NOTE "^\"|"$"
+ TerminalNode node = resolveFile(state, path);
+ if ("-f".equals(test)) { return node != null && node.isFile(); }
+ if ("-d".equals(test)) { return node != null && node.isDirectory(); }
+ if ("-e".equals(test)) { return node != null; }
+ }
+ }
+ return false;
+ }
+
+ private String replaceVariables(String text) {
+ String result = text;
+ for (Map.Entry entry : context.getVariables().entrySet())
+ { result = result.replace("$" + entry.getKey(), entry.getValue()); }
+ return result;
+ }
+
+ private TerminalNode resolveFile(TerminalState state, String fileName) {
+ TerminalDirectory current = state.getCurrentDirectory();
+ if (fileName.startsWith("/")) { return resolveAbsolutePath(state.getRootDirectory(), fileName); }
+ return resolveRelativePath(current, fileName);
+ }
+
+ private TerminalDirectory resolveDirectory(TerminalState state, String dirName) {
+ TerminalDirectory current = state.getCurrentDirectory();
+ if (dirName.startsWith("/")) { return resolveAbsoluteDirectory(state.getRootDirectory(), dirName); }
+ return resolveRelativeDirectory(current, dirName);
+ }
+
+ private TerminalNode resolveAbsolutePath(TerminalDirectory root, String path) {
+ String[] parts = path.split("/");
+ TerminalDirectory current = root;
+ for (int i = 1; i < parts.length; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) { continue; }
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) { current = current.getParent(); }
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null) { return null; }
+ if (i == parts.length - 1) { return node; }
+ if (!node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ private TerminalNode resolveRelativePath(TerminalDirectory current, String path) {
+ String[] parts = path.split("/");
+ for (int i = 0; i < parts.length; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) { continue; }
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) { current = current.getParent(); }
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null) { return null; }
+ if (i == parts.length - 1) { return node; }
+ if (!node.isDirectory()) { return null; }
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ private TerminalDirectory resolveAbsoluteDirectory(TerminalDirectory root, String path) {
+ String[] parts = path.split("/");
+ TerminalDirectory current = root;
+ for (int i = 1; i < parts.length; i++) {
+ if (parts[i].isEmpty() || ".".equals(parts[i])) continue;
+ if ("..".equals(parts[i])) {
+ if (current.getParent() != null) current = current.getParent();
+ } else {
+ TerminalNode node = current.getChild(parts[i]);
+ if (node == null || !node.isDirectory()) return null;
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ private TerminalDirectory resolveRelativeDirectory(TerminalDirectory current, String path) {
+ String[] parts = path.split("/");
+ for (String part : parts) {
+ if (part.isEmpty() || ".".equals(part)) continue;
+ if ("..".equals(part)) {
+ if (current.getParent() != null) current = current.getParent();
+ } else {
+ TerminalNode node = current.getChild(part);
+ if (node == null || !node.isDirectory()) return null;
+ current = (TerminalDirectory) node;
+ }
+ }
+ return current;
+ }
+
+ public ScriptContext getContext() { return context; }
+ public TerminalState getState() { return state; }
+ public void setState(TerminalState state) { this.state = state; }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptParser.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptParser.java
new file mode 100644
index 0000000..51b654d
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal/script/ScriptParser.java
@@ -0,0 +1,94 @@
+package gabywald.terminal.script;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Script parser for syntax analysis
+ * @author Gabriel Chandesris (2026)
+ */
+public class ScriptParser {
+
+ public static List parse(String script) {
+ List blocks = new ArrayList<>();
+ if (script == null || script.trim().isEmpty()) { return blocks; }
+
+ String[] lines = script.split("\n");
+ ScriptBlock currentBlock = new ScriptBlock(ScriptBlock.Type.NORMAL, 0);
+ int depth = 0;
+
+ for (int i = 0; i < lines.length; i++) {
+ String line = lines[i].trim();
+ if (line.isEmpty() || line.startsWith("#")) {
+ if (currentBlock.getType() != ScriptBlock.Type.NORMAL)
+ { currentBlock.addLine(line); }
+ continue;
+ }
+
+ if (line.startsWith("if ")) {
+ if (currentBlock.getType() != ScriptBlock.Type.NORMAL) { blocks.add(currentBlock); }
+ currentBlock = new ScriptBlock(ScriptBlock.Type.IF, i);
+ currentBlock.addLine(line);
+ depth++;
+ } else if (line.startsWith("while ")) {
+ if (currentBlock.getType() != ScriptBlock.Type.NORMAL) { blocks.add(currentBlock); }
+ currentBlock = new ScriptBlock(ScriptBlock.Type.WHILE, i);
+ currentBlock.addLine(line);
+ depth++;
+ } else if (line.startsWith("for ")) {
+ if (currentBlock.getType() != ScriptBlock.Type.NORMAL) { blocks.add(currentBlock); }
+ currentBlock = new ScriptBlock(ScriptBlock.Type.FOR, i);
+ currentBlock.addLine(line);
+ depth++;
+ } else if (line.equals("fi") || line.equals("done") || line.equals("end")) {
+ currentBlock.addLine(line);
+ blocks.add(currentBlock);
+ depth--;
+ if (depth >= 0) { currentBlock = new ScriptBlock(ScriptBlock.Type.NORMAL, i); }
+ } else { currentBlock.addLine(line); }
+ }
+
+ if (!currentBlock.getLines().isEmpty()) { blocks.add(currentBlock); }
+ return blocks;
+ }
+
+ public static boolean validateSyntax(String script) {
+ if (script == null) { return false; }
+ String[] lines = script.split("\n");
+ int ifDepth = 0, whileDepth = 0, forDepth = 0;
+
+ for (String line : lines) {
+ String trimmed = line.trim();
+ if (trimmed.startsWith("if ")) ifDepth++;
+ else if (trimmed.equals("fi")) ifDepth--;
+ else if (trimmed.startsWith("while ")) whileDepth++;
+ else if (trimmed.equals("done")) {
+ if (whileDepth > 0) { whileDepth--; }
+ else if (forDepth > 0) { forDepth--; }
+ }
+ else if (trimmed.startsWith("for ")) forDepth++;
+ else if (trimmed.equals("end")) forDepth--;
+ }
+ return ifDepth == 0 && whileDepth == 0 && forDepth == 0;
+ }
+
+ public static class ScriptBlock {
+ public enum Type { NORMAL, IF, WHILE, FOR }
+ private Type type;
+ private int startLine;
+ private List lines;
+
+ public ScriptBlock(Type type, int startLine) {
+ this.type = type;
+ this.startLine = startLine;
+ this.lines = new ArrayList<>();
+ }
+
+ public void addLine(String line) { this.lines.add(line); }
+ public Type getType() { return this.type; }
+ public int getStartLine() { return this.startLine; }
+ public List getLines() { return this.lines; }
+ public String getContent() { return String.join("\n", this.lines); }
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/FileSystem.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/FileSystem.java
new file mode 100644
index 0000000..0fafcf1
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/FileSystem.java
@@ -0,0 +1,330 @@
+package gabywald.terminal2;
+
+import java.util.*;
+
+/**
+ * Classe représentant un système de fichiers simplifié pour l'émulateur de terminal.
+ * Gère les fichiers et répertoires sous forme de structure arborescente.
+ * @author Gabriel Chandesris (2026)
+ */
+public class FileSystem {
+ private static final String ROOT = "/";
+ private String currentDirectory;
+ private Map> directories;
+ private Map files;
+
+ /**
+ * Constructeur : initialise le système de fichiers avec un répertoire racine.
+ */
+ public FileSystem() {
+ this.currentDirectory = FileSystem.ROOT;
+ this.directories = new HashMap<>();
+ this.files = new HashMap<>();
+ this.directories.put(FileSystem.ROOT, new HashSet<>());
+ }
+
+ /**
+ * Retourne le répertoire racine.
+ */
+ public String getRoot() { return FileSystem.ROOT; }
+
+ /**
+ * Retourne le répertoire courant.
+ */
+ public String getCurrentDirectory() { return this.currentDirectory; }
+
+ /**
+ * Change le répertoire courant.
+ * @param path Chemin absolu du nouveau répertoire courant.
+ * @throws IllegalArgumentException Si le répertoire n'existe pas.
+ */
+ public void setCurrentDirectory(String path) {
+ if (!this.directories.containsKey(path))
+ { throw new IllegalArgumentException("Répertoire introuvable: " + path); }
+ this.currentDirectory = path;
+ }
+
+ /**
+ * Crée un nouveau répertoire dans le répertoire courant.
+ * @param dirName Nom du répertoire à créer.
+ * @return true si le répertoire a été créé, false s'il existe déjà.
+ * @throws IllegalArgumentException Si dirName est vide ou null.
+ */
+ public boolean mkdir(String dirName) {
+ if (dirName == null || dirName.trim().isEmpty())
+ { throw new IllegalArgumentException("Nom de répertoire invalide."); }
+
+ String fullPath = getFullPath(dirName);
+
+ if (this.directories.containsKey(fullPath))
+ { return false; }
+
+ this.directories.put(fullPath, new HashSet<>());
+ this.directories.get(this.currentDirectory).add(dirName);
+ return true;
+ }
+
+ /**
+ * Supprime un fichier ou un répertoire (doit être vide).
+ * @param name Nom du fichier ou répertoire à supprimer.
+ * @return true si la suppression a réussi, false sinon.
+ */
+ public boolean rm(String name) {
+ if (name == null || name.trim().isEmpty()) { return false; }
+
+ String fullPath = this.getFullPath(name);
+
+ if (this.files.containsKey(fullPath)) {
+ this.files.remove(fullPath);
+ this.directories.get(this.currentDirectory).remove(name);
+ return true;
+ } else if (this.directories.containsKey(fullPath)) {
+ Set contents = this.directories.get(fullPath);
+ if (contents.isEmpty()) {
+ this.directories.remove(fullPath);
+ this.directories.get(this.currentDirectory).remove(name);
+ return true;
+ }
+ return false;
+ }
+
+ return false;
+ }
+
+ /**
+ * Supprime un répertoire et tout son contenu (récursif).
+ * @param name Nom du répertoire à supprimer.
+ * @return true si la suppression a réussi, false sinon.
+ */
+ public boolean rmdir(String name) {
+ if (name == null || name.trim().isEmpty()) { return false; }
+
+ String fullPath = getFullPath(name);
+
+ if (!this.directories.containsKey(fullPath)) { return false; }
+
+ Set contents = new HashSet<>(this.directories.get(fullPath));
+ for (String item : contents) {
+ String itemPath = fullPath.equals(ROOT) ? ROOT + item : fullPath + "/" + item;
+ if (this.directories.containsKey(itemPath)) {
+ rmdir(item);
+ } else if (this.files.containsKey(itemPath)) {
+ this.files.remove(itemPath);
+ }
+ }
+
+ this.directories.remove(fullPath);
+ this.directories.get(this.currentDirectory).remove(name);
+ return true;
+ }
+
+ /**
+ * Liste le contenu du répertoire courant.
+ * @return Liste des noms de fichiers et répertoires (les répertoires se terminent par "/").
+ */
+ public List ls() {
+ List contents = new ArrayList<>();
+ Set dirContents = this.directories.getOrDefault(this.currentDirectory, new HashSet<>());
+
+ for (String item : dirContents) {
+ String fullPath = this.getFullPath(item);
+ if (this.directories.containsKey(fullPath)) {
+ contents.add(item + "/");
+ } else if (this.files.containsKey(fullPath)) {
+ contents.add(item);
+ }
+ }
+
+ Collections.sort(contents);
+ return contents;
+ }
+
+ /**
+ * Liste le contenu d'un répertoire spécifique.
+ * @param dirPath Chemin du répertoire à lister.
+ * @return Liste des noms de fichiers et répertoires.
+ * @throws IllegalArgumentException Si le répertoire n'existe pas.
+ */
+ public List ls(String dirPath) {
+ if (!this.directories.containsKey(dirPath))
+ { throw new IllegalArgumentException("Répertoire introuvable: " + dirPath); }
+
+ List contents = new ArrayList<>();
+ Set dirContents = this.directories.get(dirPath);
+
+ for (String item : dirContents) {
+ String fullPath = dirPath.equals(ROOT) ? ROOT + item : dirPath + "/" + item;
+ if (this.directories.containsKey(fullPath)) {
+ contents.add(item + "/");
+ } else if (this.files.containsKey(fullPath)) {
+ contents.add(item);
+ }
+ }
+
+ Collections.sort(contents);
+ return contents;
+ }
+
+ /**
+ * Crée un nouveau fichier vide.
+ * @param fileName Nom du fichier à créer.
+ * @return true si le fichier a été créé, false s'il existe déjà.
+ * @throws IllegalArgumentException Si fileName est vide ou null.
+ */
+ public boolean touch(String fileName) {
+ if (fileName == null || fileName.trim().isEmpty())
+ { throw new IllegalArgumentException("Nom de fichier invalide."); }
+
+ String fullPath = this.getFullPath(fileName);
+
+ if (this.files.containsKey(fullPath)) { return false; }
+
+ this.files.put(fullPath, "");
+ this.directories.get(this.currentDirectory).add(fileName);
+ return true;
+ }
+
+ /**
+ * Lit le contenu d'un fichier.
+ * @param fileName Nom du fichier à lire.
+ * @return Contenu du fichier, ou un message d'erreur si le fichier n'existe pas.
+ */
+ public String cat(String fileName) {
+ String fullPath = this.getFullPath(fileName);
+ return this.files.getOrDefault(fullPath, "Fichier introuvable: " + fileName);
+ }
+
+ /**
+ * Écrit du contenu dans un fichier (écrasement).
+ * @param fileName Nom du fichier.
+ * @param content Contenu à écrire.
+ * @return true si l'écriture a réussi, false sinon.
+ */
+ public boolean echo(String fileName, String content) {
+ if (fileName == null || fileName.trim().isEmpty()) { return false; }
+
+ String fullPath = this.getFullPath(fileName);
+ this.files.put(fullPath, content);
+ return true;
+ }
+
+ /**
+ * Ajoute du contenu à la fin d'un fichier.
+ * @param fileName Nom du fichier.
+ * @param content Contenu à ajouter.
+ * @return true si l'ajout a réussi, false sinon.
+ */
+ public boolean append(String fileName, String content) {
+ if (fileName == null || fileName.trim().isEmpty()) { return false; }
+
+ String fullPath = getFullPath(fileName);
+ String existingContent = this.files.getOrDefault(fullPath, "");
+ this.files.put(fullPath, existingContent + content);
+ return true;
+ }
+
+ /**
+ * Construit le chemin absolu à partir d'un nom de fichier/répertoire.
+ * @param name Nom du fichier ou répertoire.
+ * @return Chemin absolu.
+ */
+ private String getFullPath(String name) {
+ if (this.currentDirectory.equals(FileSystem.ROOT))
+ { return FileSystem.ROOT + name; }
+ return this.currentDirectory + "/" + name;
+ }
+
+ /**
+ * Vérifie si un fichier ou répertoire existe.
+ * @param name Nom du fichier ou répertoire.
+ * @return true s'il existe, false sinon.
+ */
+ public boolean exists(String name) {
+ String fullPath = getFullPath(name);
+ return this.files.containsKey(fullPath) || this.directories.containsKey(fullPath);
+ }
+
+ /**
+ * Retourne le chemin absolu du répertoire parent.
+ * @param path Chemin dont on veut le parent.
+ * @return Chemin absolu du répertoire parent.
+ */
+ public String getParentDirectory(String path) {
+ if (path.equals(FileSystem.ROOT)) { return FileSystem.ROOT; }
+
+ String[] parts = path.split("/");
+ if (parts.length <= 2) { return FileSystem.ROOT; }
+
+ StringBuilder parentPath = new StringBuilder();
+ for (int i = 1; i < parts.length - 1; i++)
+ { parentPath.append("/").append(parts[i]); }
+
+ return parentPath.length() == 0 ? FileSystem.ROOT : parentPath.toString();
+ }
+
+ /**
+ * Résout un chemin (relatif ou absolu) en chemin absolu.
+ * @param path Chemin à résoudre (peut être relatif ou absolu).
+ * @return Chemin absolu résolu.
+ */
+ public String resolvePath(String path) {
+ if (path == null || path.trim().isEmpty())
+ { return this.currentDirectory; }
+
+ path = path.trim();
+
+ if (path.startsWith(ROOT))
+ { return this.normalizePath(path); }
+
+ String resolvedPath = this.currentDirectory + "/" + path;
+ return normalizePath(resolvedPath);
+ }
+
+ /**
+ * Normalise un chemin (supprime les "/./" et résout les "/../").
+ * @param path Chemin à normaliser.
+ * @return Chemin normalisé.
+ */
+ public String normalizePath(String path) {
+ if (path.equals(FileSystem.ROOT)) { return FileSystem.ROOT; }
+
+ String[] parts = path.split("/");
+ List normalizedParts = new ArrayList<>();
+
+ for (String part : parts) {
+ if (part.isEmpty() || part.equals("."))
+ { continue; }
+ else if (part.equals("..")) {
+ if (!normalizedParts.isEmpty()) {
+ normalizedParts.remove(normalizedParts.size() - 1);
+ }
+ }
+ else { normalizedParts.add(part); }
+ }
+
+ if (normalizedParts.isEmpty())
+ { return FileSystem.ROOT; }
+
+ StringBuilder normalizedPath = new StringBuilder();
+ for (String part : normalizedParts)
+ { normalizedPath.append("/").append(part); }
+
+ return normalizedPath.toString();
+ }
+
+ /**
+ * Vérifie si un chemin est un répertoire.
+ * @param path Chemin à vérifier.
+ * @return true si c'est un répertoire, false sinon.
+ */
+ public boolean isDirectory(String path)
+ { return this.directories.containsKey(path); }
+
+ /**
+ * Vérifie si un chemin est un fichier.
+ * @param path Chemin à vérifier.
+ * @return true si c'est un fichier, false sinon.
+ */
+ public boolean isFile(String path)
+ { return this.files.containsKey(path); }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/MainTerminal2.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/MainTerminal2.java
new file mode 100644
index 0000000..4c71466
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/MainTerminal2.java
@@ -0,0 +1,10 @@
+package gabywald.terminal2;
+
+/**
+ * Classe principale pour lancer l'émulateur de terminal.
+ * @author Gabriel Chandesris (2026)
+ * @deprecated Use {@code gabywald.launchers.TerminalEmulatorLaunchers}
+ */
+public class MainTerminal2 {
+ public static void main(String[] args) { new TerminalEmulator(); }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/Pipe.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/Pipe.java
new file mode 100644
index 0000000..19edac7
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/Pipe.java
@@ -0,0 +1,39 @@
+package gabywald.terminal2;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Classe pour gérer les pipes entre commandes.
+ * @author Gabriel Chandesris (2026)
+ */
+public class Pipe {
+ private List commands;
+
+ /**
+ * Constructeur.
+ * @param commands Liste des commandes séparées par des pipes.
+ */
+ public Pipe(List commands) { this.commands = commands; }
+
+ /**
+ * Retourne la liste des commandes.
+ */
+ public List getCommands() { return this.commands; }
+
+ /**
+ * Analyse une chaîne de commandes avec pipes.
+ * @param input Chaîne complète (ex: "ls | grep txt").
+ * @return Objet Pipe contenant les commandes séparées.
+ */
+ public static Pipe parsePipe(String input) {
+ String[] parts = input.split("\\|");
+ List commandList = new ArrayList<>();
+ for (String part : parts) {
+ String trimmedPart = part.trim();
+ if (!trimmedPart.isEmpty())
+ { commandList.add(trimmedPart.split("\\s+")); }
+ }
+ return new Pipe(commandList);
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/Redirection.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/Redirection.java
new file mode 100644
index 0000000..42ca9b4
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/Redirection.java
@@ -0,0 +1,86 @@
+package gabywald.terminal2;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Classe pour gérer les redirections d'entrée/sortie (>, >>, <).
+ * @author Gabriel Chandesris (2026)
+ */
+public class Redirection {
+ /**
+ * Type de redirection.
+ */
+ public enum Type {
+ NONE, // Pas de redirection
+ OUTPUT, // > (écrasement)
+ APPEND, // >> (ajout)
+ INPUT // < (entrée)
+ }
+
+ private Type type;
+ private String fileName;
+ public String[] cleanedArgs; // Arguments nettoyés (sans les redirections)
+
+ /**
+ * Constructeur.
+ * @param type Type de redirection.
+ * @param fileName Nom du fichier.
+ */
+ public Redirection(Type type, String fileName) {
+ this.type = type;
+ this.fileName = fileName;
+ }
+
+ /**
+ * Retourne le type de redirection.
+ */
+ public Type getType() { return this.type; }
+
+ /**
+ * Retourne le nom du fichier.
+ */
+ public String getFileName() { return this.fileName; }
+
+ /**
+ * Analyse une commande pour extraire les redirections.
+ * @param args Arguments de la commande.
+ * @return Liste des redirections trouvées.
+ */
+ public static List parseRedirections(String[] args) {
+ List redirections = new ArrayList<>();
+ List cleanedArgs = new ArrayList<>();
+
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if (arg.equals(">") && i + 1 < args.length) {
+ redirections.add(new Redirection(Type.OUTPUT, args[i + 1]));
+ i++;
+ } else if (arg.equals(">>") && i + 1 < args.length) {
+ redirections.add(new Redirection(Type.APPEND, args[i + 1]));
+ i++;
+ } else if (arg.equals("<") && i + 1 < args.length) {
+ redirections.add(new Redirection(Type.INPUT, args[i + 1]));
+ i++;
+ } else { cleanedArgs.add(arg); }
+ }
+
+ if (!cleanedArgs.isEmpty() && !redirections.isEmpty()) {
+ redirections.get(0).cleanedArgs = cleanedArgs.toArray(new String[0]);
+ }
+
+ return redirections;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sbToReturn = new StringBuilder();
+
+ sbToReturn.append(this.fileName).append(this.type.name()).append("\n");
+ Arrays.asList(this.cleanedArgs).stream().forEach(elt -> sbToReturn.append("\t").append(elt).append("\n"));
+
+ return sbToReturn.toString();
+ }
+
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/TerminalEmulator.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/TerminalEmulator.java
new file mode 100644
index 0000000..5cdae40
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/TerminalEmulator.java
@@ -0,0 +1,222 @@
+package gabywald.terminal2;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Font;
+
+import javax.swing.JFrame;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.JTextPane;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyleContext;
+import javax.swing.text.StyledDocument;
+
+import gabywald.terminal2.commands.CommandParser;
+import gabywald.terminal2.editors.NanoEditorSwing;
+import gabywald.terminal2.editors.TextEditorSwing;
+import gabywald.terminal2.editors.VimEditorSwing;
+
+/**
+ * Classe principale pour l'interface graphique de l'émulateur de terminal.
+ * Gère l'affichage, la saisie des commandes, et les modes d'édition.
+ * @author Gabriel Chandesris (2026)
+ */
+public class TerminalEmulator {
+ private JFrame frame;
+ private JTextPane outputArea;
+ private JTextField inputField;
+ private FileSystem fileSystem;
+ private CommandParser commandParser;
+ private String currentDirectory;
+
+ // For Edition Mode
+ private TextEditorSwing currentEditor;
+ private String currentEditFile;
+ private boolean isEditing = false;
+
+ /**
+ * Constructor : initialize graphical Interface and File System
+ */
+ public TerminalEmulator() {
+ this.fileSystem = new FileSystem();
+ this.commandParser = new CommandParser(this.fileSystem);
+ this.currentDirectory = this.fileSystem.getRoot();
+ this.initializeUI();
+ }
+
+ /**
+ * Initialise User Interface (UI).
+ */
+ private void initializeUI() {
+ this.frame = new JFrame("Terminal Emulator");
+ this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ this.frame.setSize(800, 600);
+ this.frame.setLayout(new BorderLayout());
+
+ // Not editable output Zone
+ this.outputArea = new JTextPane();
+ this.outputArea.setEditable(false);
+ this.outputArea.setBackground(Color.BLACK);
+ this.outputArea.setForeground(Color.GREEN);
+ this.outputArea.setFont(new Font("Monospaced", Font.PLAIN, 14));
+ JScrollPane scrollPane = new JScrollPane(this.outputArea);
+ this.frame.add(scrollPane, BorderLayout.CENTER);
+
+ // Input Field
+ this.inputField = new JTextField();
+ this.inputField.setBackground(Color.BLACK);
+ this.inputField.setForeground(Color.GREEN);
+ this.inputField.setFont(new Font("Monospaced", Font.PLAIN, 14));
+ this.inputField.addActionListener(e -> executeCommand());
+ this.frame.add(this.inputField, BorderLayout.SOUTH);
+
+ // Show Welcome Message
+ this.printWelcomeMessage();
+ // Show Initial Prompt
+ this.printPrompt();
+
+ this.frame.setVisible(true);
+ }
+
+ private void printWelcomeMessage() {
+ this.appendToOutput("===============================================\n");
+ this.appendToOutput(" TERMINAL EMULATOR (V2) - Java 8 / Swing\n");
+ this.appendToOutput(" Type 'help' for a list of available commands\n");
+ // this.appendToOutput(" Type 'exit' to quit\n");
+ this.appendToOutput("===============================================\n\n");
+ }
+
+ /**
+ * Show Prompt in Output Zone
+ */
+ private void printPrompt() {
+ try {
+ StyledDocument doc = this.outputArea.getStyledDocument();
+ doc.insertString(doc.getLength(), currentDirectory + " > ", getPromptStyle());
+ } catch (BadLocationException ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ /**
+ * Return Prompt Stule
+ */
+ private Style getPromptStyle() {
+ StyleContext sc = StyleContext.getDefaultStyleContext();
+ Style style = sc.addStyle("PromptStyle", null);
+ StyleConstants.setForeground(style, Color.GREEN);
+ StyleConstants.setBold(style, true);
+ return style;
+ }
+
+ /**
+ * Execute command given by User
+ */
+ private void executeCommand() {
+ String command = this.inputField.getText().trim();
+ this.inputField.setText("");
+
+ if (command.isEmpty()) {
+ this.printPrompt();
+ return;
+ }
+
+ this.appendToOutput(command + "\n");
+
+ if (this.isEditing) {
+ this.handleEditorInput(command);
+ return;
+ }
+
+ if (command.startsWith("edit ")) {
+ this.startEditMode(command.substring(5).trim());
+ return;
+ }
+
+ String output = this.commandParser.execute(command, this.currentDirectory);
+ if (output.startsWith("MODE_EDIT:"))
+ { this.startEditMode(output.substring(10)); }
+ else
+ { this.appendToOutput(output + "\n"); }
+
+ if (command.startsWith("cd "))
+ { this.currentDirectory = this.fileSystem.getCurrentDirectory(); }
+
+ this.printPrompt();
+ }
+
+ /**
+ * Start Editing mode (nano or vim)
+ */
+ private void startEditMode(String args) {
+ this.isEditing = true;
+ String[] editArgs = args.split("\\s+");
+ String editorType = "nano";
+ String fileName;
+
+ if (editArgs.length > 0 && (editArgs[0].equals("--nano") || editArgs[0].equals("--vim"))) {
+ editorType = editArgs[0].substring(2);
+ if (editArgs.length < 2) {
+ appendToOutput("Usage: edit [--nano|--vim] \n");
+ this.isEditing = false;
+ printPrompt();
+ return;
+ }
+ fileName = editArgs[1];
+ } else { fileName = editArgs[0]; }
+
+ this.currentEditFile = fileName;
+
+ if (editorType.equals("nano"))
+ { this.currentEditor = new NanoEditorSwing(this, this.fileSystem, fileName); }
+ else
+ { this.currentEditor = new VimEditorSwing(this, this.fileSystem, fileName); }
+
+ this.appendToOutput("--- Edition with '" + editorType + "' ---\n");
+ this.currentEditor.start();
+ }
+
+ /**
+ * Gère les entrées en mode édition.
+ */
+ private void handleEditorInput(String input) {
+ String result = this.currentEditor.handleInput(input);
+ if (result != null) {
+ if (result.startsWith("SAVE:")) {
+ this.fileSystem.echo(this.currentEditFile, result.substring(5));
+ this.appendToOutput("File '" + this.currentEditFile + "' recorded.\n");
+ } else if (result.equals("CANCEL")) {
+ this.appendToOutput("Edition Canceled.\n");
+ }
+ this.endEditMode();
+ } else {
+ appendToOutput(input + "\n");
+ }
+ }
+
+ /**
+ * End Edtion Mode
+ */
+ private void endEditMode() {
+ this.isEditing = false;
+ this.currentEditor = null;
+ this.currentEditFile = null;
+ this.printPrompt();
+ }
+
+ /**
+ * Add Text to Output Zone
+ */
+ public void appendToOutput(String text) {
+ try {
+ StyledDocument doc = this.outputArea.getStyledDocument();
+ doc.insertString(doc.getLength(), text, null);
+ this.outputArea.setCaretPosition(doc.getLength());
+ } catch (BadLocationException ex) {
+ ex.printStackTrace();
+ }
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CatCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CatCommand.java
new file mode 100644
index 0000000..fc2741a
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CatCommand.java
@@ -0,0 +1,31 @@
+package gabywald.terminal2.commands;
+
+import gabywald.terminal2.FileSystem;
+
+/**
+ * Commande 'cat' : affiche le contenu d'un fichier ou de stdin.
+ * @author Gabriel Chandesris (2026)
+ */
+public class CatCommand implements Command {
+ private FileSystem fileSystem;
+
+ public CatCommand(FileSystem fileSystem) { this.fileSystem = fileSystem; }
+
+ @Override
+ public String execute(String[] args, String stdin) {
+ if (args.length == 0) {
+ if (stdin != null) { return stdin; }
+ return "Usage: cat ou utiliser une redirection (<)";
+ }
+
+ StringBuilder output = new StringBuilder();
+ for (String fileName : args) {
+ if (this.fileSystem.exists(fileName)) {
+ output.append(this.fileSystem.cat(fileName));
+ } else {
+ output.append("Fichier introuvable: ").append(fileName);
+ }
+ }
+ return output.toString();
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CdCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CdCommand.java
new file mode 100644
index 0000000..05498e6
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CdCommand.java
@@ -0,0 +1,38 @@
+package gabywald.terminal2.commands;
+
+import gabywald.terminal2.FileSystem;
+
+/**
+ * Commande 'cd' : change le répertoire courant.
+ * @author Gabriel Chandesris (2026)
+ */
+public class CdCommand implements Command {
+ private FileSystem fileSystem;
+
+ public CdCommand(FileSystem fileSystem) {
+ this.fileSystem = fileSystem;
+ }
+
+ @Override
+ public String execute(String[] args, String stdin) {
+ if (args.length == 0) { return "Usage: cd "; }
+
+ String targetDir = args[0];
+ if (targetDir.equals("..")) {
+ String parentDir = fileSystem.getParentDirectory(fileSystem.getCurrentDirectory());
+ this.fileSystem.setCurrentDirectory(parentDir);
+ return "";
+ } else if (targetDir.equals("/")) {
+ fileSystem.setCurrentDirectory(fileSystem.getRoot());
+ return "";
+ } else {
+ String currentDir = fileSystem.getCurrentDirectory();
+ String fullPath = currentDir.equals("/") ? "/" + targetDir : currentDir + "/" + targetDir;
+ if (fileSystem.exists(targetDir)) {
+ fileSystem.setCurrentDirectory(fullPath);
+ return "";
+ }
+ return "Répertoire introuvable: " + targetDir;
+ }
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/ClearCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/ClearCommand.java
new file mode 100644
index 0000000..ef0f8eb
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/ClearCommand.java
@@ -0,0 +1,11 @@
+package gabywald.terminal2.commands;
+
+/**
+ * Commande 'clear' : efface l'écran (simulé par des sauts de ligne).
+ * @author Gabriel Chandesris (2026)
+ */
+public class ClearCommand implements Command {
+ @Override
+ public String execute(String[] args, String stdin)
+ { return "\n\n\n\n\n\n\n\n\n\n"; }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/Command.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/Command.java
new file mode 100644
index 0000000..6693844
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/Command.java
@@ -0,0 +1,23 @@
+package gabywald.terminal2.commands;
+
+/**
+ * Interface pour les commandes du terminal.
+ * Supporte les entrées standard (stdin) pour les pipes et redirections.
+ * @author Gabriel Chandesris (2026)
+ */
+public interface Command {
+ /**
+ * Exécute la commande avec les arguments fournis.
+ * @param args Arguments de la commande.
+ * @return Résultat de l'exécution.
+ */
+ default String execute(String[] args) { return this.execute(CommandParser.removefirstFrom(args), null); }
+
+ /**
+ * Exécute la commande avec une entrée standard (pour les pipes et redirections <).
+ * @param args Arguments de la commande.
+ * @param stdin Entrée standard (peut être null).
+ * @return Résultat de l'exécution.
+ */
+ String execute(String[] args, String stdin);
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CommandParser.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CommandParser.java
new file mode 100644
index 0000000..9852694
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/CommandParser.java
@@ -0,0 +1,154 @@
+package gabywald.terminal2.commands;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import gabywald.terminal2.FileSystem;
+import gabywald.terminal2.Pipe;
+import gabywald.terminal2.Redirection;
+
+/**
+ * Classe responsable de l'analyse et de l'exécution des commandes.
+ * Supporte les redirections (>, >>, <) et les pipes (|).
+ * @author Gabriel Chandesris (2026)
+ */
+public class CommandParser {
+ private FileSystem fileSystem;
+ private Map commands;
+
+ /**
+ * Constructeur : initialise les commandes disponibles.
+ */
+ public CommandParser(FileSystem fileSystem) {
+ this.fileSystem = fileSystem;
+ this.commands = new HashMap<>();
+ this.initializeCommands();
+ }
+
+ /**
+ * Initialise les commandes disponibles.
+ */
+ private void initializeCommands() {
+ this.commands.put("ls", new LsCommand(fileSystem));
+ this.commands.put("cd", new CdCommand(fileSystem));
+ this.commands.put("cat", new CatCommand(fileSystem));
+ this.commands.put("echo", new EchoCommand(fileSystem));
+ this.commands.put("mkdir", new MkdirCommand(fileSystem));
+ this.commands.put("touch", new TouchCommand(fileSystem));
+ this.commands.put("rm", new RmCommand(fileSystem));
+ this.commands.put("pwd", new PwdCommand(fileSystem));
+ this.commands.put("help", new HelpCommand());
+ this.commands.put("clear", new ClearCommand());
+ this.commands.put("edit", new EditCommand(fileSystem, this));
+ this.commands.put("run", new RunCommand(fileSystem, this));
+ this.commands.put("grep", new GrepCommand(fileSystem));
+ this.commands.put("wc", new WcCommand(fileSystem));
+ this.commands.put("tr", new TrCommand(fileSystem));
+ this.commands.put("sort", new SortCommand(fileSystem));
+ }
+
+ /**
+ * Exécute une commande avec gestion des redirections et pipes.
+ * @param input Commande complète (ex: "ls | grep txt").
+ * @param currentDirectory Répertoire courant.
+ * @return Résultat de l'exécution.
+ */
+ public String execute(String input, String currentDirectory) {
+ input = input.trim();
+ if (input.isEmpty()) { return ""; }
+
+ if (input.contains("|")) { return this.executePipe(input); }
+
+ String[] args = input.split("\\s+");
+ List redirections = Redirection.parseRedirections(args);
+
+ if (redirections.isEmpty())
+ { return this.executeCommand(args, currentDirectory, null); }
+ else
+ { return this.executeWithRedirections(redirections, currentDirectory); }
+ }
+
+ /**
+ * Execute command with Redirections
+ */
+ private String executeWithRedirections(List redirections, String currentDirectory) {
+ Redirection firstRedirection = redirections.get(0);
+ String[] args = firstRedirection.cleanedArgs;
+
+ if (args.length == 0) { return "Commande invalide."; }
+
+ String commandName = args[0];
+ Command command = this.commands.get(commandName);
+ if (command == null) { return "Commande introuvable: " + commandName; }
+
+ String stdin = null;
+ for (Redirection r : redirections) {
+ if (r.getType() == Redirection.Type.INPUT) {
+ if (this.fileSystem.exists(r.getFileName()))
+ { stdin = this.fileSystem.cat(r.getFileName()); }
+ else
+ { return "Fichier introuvable pour l'entrée: " + r.getFileName(); }
+ }
+ }
+
+ String output = command.execute(CommandParser.removefirstFrom(args), stdin);
+
+ for (Redirection r : redirections) {
+ if (r.getType() == Redirection.Type.OUTPUT || r.getType() == Redirection.Type.APPEND) {
+ if (r.getType() == Redirection.Type.OUTPUT)
+ { this.fileSystem.echo(r.getFileName(), output); }
+ else { // Redirection.Type.APPEND
+ String existingContent = this.fileSystem.exists(r.getFileName()) ?
+ this.fileSystem.cat(r.getFileName()) : "";
+ this.fileSystem.echo(r.getFileName(), existingContent + output);
+ }
+ return "";
+ }
+ }
+
+ return output;
+ }
+
+ /**
+ * Execute Command without Redirection
+ */
+ private String executeCommand(String[] args, String currentDirectory, String stdin) {
+ if (args.length == 0) { return ""; }
+
+ String commandName = args[0];
+ Command command = this.commands.get(commandName);
+ if (command == null) { return "Commande introuvable: " + commandName; }
+
+ return command.execute(CommandParser.removefirstFrom(args), stdin);
+ }
+
+ /**
+ * Remove first elt of agrs ('command').
+ */
+ static String[] removefirstFrom(String[] args)
+ { return Arrays.asList(args).subList(1, args.length).toArray(new String[args.length - 1]); }
+
+ /**
+ * Execute chain of Commands with pipes
+ */
+ private String executePipe(String input) {
+ Pipe pipe = Pipe.parsePipe(input);
+ List commandList = pipe.getCommands();
+
+ if (commandList.isEmpty()) { return ""; }
+
+ String currentOutput = this.executeCommand(commandList.get(0), this.fileSystem.getCurrentDirectory(), null);
+
+ for (int i = 1; i < commandList.size(); i++) {
+ String[] args = commandList.get(i);
+ String commandName = args[0];
+ Command command = this.commands.get(commandName);
+ if (command == null) { return "Commande introuvable dans le pipe: " + commandName; }
+ currentOutput = command.execute(CommandParser.removefirstFrom(args), currentOutput);
+ }
+
+ return currentOutput;
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/EchoCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/EchoCommand.java
new file mode 100644
index 0000000..6f7dac0
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/EchoCommand.java
@@ -0,0 +1,22 @@
+package gabywald.terminal2.commands;
+
+import gabywald.terminal2.FileSystem;
+
+/**
+ * Commande 'echo' : affiche du texte.
+ * @author Gabriel Chandesris (2026)
+ */
+public class EchoCommand implements Command {
+ private FileSystem fileSystem;
+
+ public EchoCommand(FileSystem fileSystem) { this.fileSystem = fileSystem; }
+
+ @Override
+ public String execute(String[] args, String stdin) {
+ if (args.length == 0) { return "Usage: echo "; }
+
+ StringBuilder text = new StringBuilder();
+ for (String arg : args) { text.append(arg).append(" "); } // .replaceAll("\"", "")
+ return text.toString().trim();
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/EditCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/EditCommand.java
new file mode 100644
index 0000000..5c3f3d5
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/EditCommand.java
@@ -0,0 +1,27 @@
+package gabywald.terminal2.commands;
+
+import gabywald.terminal2.FileSystem;
+
+/**
+ * Commande 'edit' : lance un éditeur de texte (nano ou vim).
+ * Usage:
+ * edit # Utilise nano par défaut
+ * edit --nano # Force nano
+ * edit --vim # Force vim
+ * @author Gabriel Chandesris (2026)
+ */
+public class EditCommand implements Command {
+ private FileSystem fileSystem;
+ private CommandParser commandParser;
+
+ public EditCommand(FileSystem fileSystem, CommandParser commandParser) {
+ this.fileSystem = fileSystem;
+ this.commandParser = commandParser;
+ }
+
+ @Override
+ public String execute(String[] args, String stdin) {
+ if (args.length == 0) { return "Usage: edit [--nano|--vim] "; }
+ return "MODE_EDIT:" + String.join(" ", args);
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/GrepCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/GrepCommand.java
new file mode 100644
index 0000000..ba92f30
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/GrepCommand.java
@@ -0,0 +1,40 @@
+package gabywald.terminal2.commands;
+
+import gabywald.terminal2.FileSystem;
+
+/**
+ * Commande 'grep' : filtre les lignes contenant un motif.
+ * Usage: grep [fichier] ou grep < stdin
+ * @author Gabriel Chandesris (2026)
+ */
+public class GrepCommand implements Command {
+ private FileSystem fileSystem;
+
+ public GrepCommand(FileSystem fileSystem) { this.fileSystem = fileSystem; }
+
+ @Override
+ public String execute(String[] args, String stdin) {
+ if (args.length == 0) { return "Usage: grep [file]"; }
+
+ String pattern = args[0];
+ String content;
+
+ if (args.length > 1) {
+ String fileName = args[1];
+ if (!this.fileSystem.exists(fileName))
+ { return "Fichier introuvable: " + fileName; }
+ content = this.fileSystem.cat(fileName);
+ } else if (stdin != null) {
+ content = stdin;
+ } else {
+ return "Usage: grep [file] or use pipe";
+ }
+
+ StringBuilder output = new StringBuilder();
+ for (String line : content.split("\n")) {
+ if (line.contains(pattern)) { output.append(line).append("\n"); }
+ }
+
+ return output.toString();
+ }
+}
diff --git a/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/HelpCommand.java b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/HelpCommand.java
new file mode 100644
index 0000000..17fbbdc
--- /dev/null
+++ b/testideas-terminalEmulator/src/main/java/gabywald/terminal2/commands/HelpCommand.java
@@ -0,0 +1,36 @@
+package gabywald.terminal2.commands;
+
+/**
+ * Command 'help' : Show Availaible Command List.
+ * @author Gabriel Chandesris (2026)
+ */
+public class HelpCommand implements Command {
+ @Override
+ public String execute(String[] args, String stdin) {
+ return "Commandes disponibles:\n" +
+ " ls - Lister les fichiers/répertoires\n" +
+ " cd - Changer de répertoire\n" +
+ " cat - Afficher le contenu d'un fichier\n" +
+ " echo - Afficher du texte\n" +
+ " echo > - Écrire dans un fichier (écrasement)\n" +
+ " echo >> - Ajouter à un fichier\n" +
+ " mkdir - Créer un répertoire\n" +
+ " touch - Créer un fichier\n" +
+ " rm - Supprimer un fichier/répertoire\n" +
+ " pwd - Afficher le répertoire courant\n" +
+ " edit - Éditer un fichier (nano par défaut)\n" +
+ " edit --nano - Éditer avec nano\n" +
+ " edit --vim - Éditer avec vim\n" +
+ " run