feat(editor): F4.6b the command line on a computer's desktop
A new oracle, TerminalWindowRun, types into Java's own command line window: the window writes each line after its prompt, takes a blank line, cls, reset and exit itself, keeps the lines for the arrow keys, and keeps its text, history and working directory over a stop and a start. The room now holds a command line window per computer for everyone (TerminalWindow), kept over stops; the protocol carries its lines and the one way to read them (terminalScreen); the core goes on in the working directory of the run before (continueIn). The desktop opens programs in Java's frame, and the command line works with keyboard and finger: grey on black, the prompt hidden while a command runs, history, chips for the commands, Ctrl+C. The room's failure is now tested in the browser. Fixed: the room handed the engine the wall clock with fractions of a millisecond, and a ping hung after its first timeout. The engine and the room count whole milliseconds, as Java's clock does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1239,3 +1239,29 @@ Gemessen (03.10.2026):
|
||||
Karte; ohne Karte behält der Router kein Gateway (`setStandardGateway`
|
||||
schreibt es in die Karten, `getStandardGateway` liest `null`).
|
||||
|
||||
### Das Fenster der Befehlszeile (F4.6b)
|
||||
|
||||
`TerminalWindowRun` öffnet Javas `GUIApplicationTerminalWindow` auf einem
|
||||
`GUIDesktopPanel` eines Rechners ohne Netz, gibt Tasten direkt an den
|
||||
Tastenhörer des Eingabefelds (Enter, Pfeil hoch/runter), stoppt und startet
|
||||
das System einmal (`beenden`, `starten`, wie ein Moduswechsel) und schreibt
|
||||
nach jeder Taste Text, Prompt, ob er sichtbar ist, und die Eingabezeile
|
||||
(`packages/sim/test/terminalWindow.java.json`).
|
||||
|
||||
Gemessen (03.10.2026):
|
||||
|
||||
* Eine leere Zeile, auch nur aus Leerzeichen (`StringUtils.isBlank`), gibt
|
||||
„␣␣\n“; jede andere Zeile steht danach als „\n“ + Prompt + Zeile + „\n“
|
||||
im Fenster, auch `cls`, `reset` und `exit`, bevor das Fenster sie selbst
|
||||
erledigt.
|
||||
* `cls`/`reset` zeigen wieder den Startbildschirm; `exit` schließt das
|
||||
Fenster, sein Text bleibt.
|
||||
* Pfeil hoch geht durch alle eingegebenen Zeilen zurück (leere nicht),
|
||||
runter vor, unter der neuesten steht eine leere Zeile; über die Enden
|
||||
hinaus bleibt es stehen.
|
||||
* Der Prompt folgt dem Arbeitsverzeichnis, nachgeführt, wenn ein Befehl
|
||||
fertig ist (`cd dir` → „/dir> “).
|
||||
* Nach Stopp und Start bleiben Text, Verlauf **und** Arbeitsverzeichnis:
|
||||
Javas Terminal setzt den Ordner nur bei der Installation
|
||||
(`setSystemSoftware`), und das Fenster ist dasselbe Objekt.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
|
||||
fi
|
||||
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
|
||||
mkdir -p "$here/.build"
|
||||
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java"
|
||||
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java" "$here/src/WebRun.java" "$here/src/RouterWebRun.java" "$here/src/KonfigCases.java" "$here/src/EmailRun.java" "$here/src/MailCases.java" "$here/src/EmailClientRun.java" "$here/src/TerminalCases.java" "$here/src/TerminalRun.java" "$here/src/FileCases.java" "$here/src/GnutellaRun.java" "$here/src/NewDeviceRun.java" "$here/src/SheetRun.java" "$here/src/TerminalWindowRun.java"
|
||||
# FILIUS writes its settings below $HOME; keep them out of the real one.
|
||||
mkdir -p "$here/.build/home"
|
||||
# Removed first: a crashed run must not leave the previous result standing.
|
||||
@@ -337,6 +337,14 @@ HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" SheetRun
|
||||
"$root/packages/doc/test/sheet.cases.txt" "$output" 2>&1 | grep -v 'logback\|^$' || true
|
||||
test -s "$output"
|
||||
|
||||
# FILIUS' command line window as it shows what is typed into it, key by key,
|
||||
# over a stop and a start (TerminalWindowRun): the room's window
|
||||
# (packages/sim/test/terminalWindow.test.ts).
|
||||
output="$root/packages/sim/test/terminalWindow.java.json"
|
||||
rm -f "$output"
|
||||
HOME="$here/.build/home" xvfb-run -a java -Duser.language=de -Duser.country=DE -cp "$classpath:$here/.build" TerminalWindowRun "$output" 2>&1 | grep -v 'logback\|^$' || true
|
||||
test -s "$output"
|
||||
|
||||
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
|
||||
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts)
|
||||
# and the classics of F3 (web, email, gnutella; packages/fls/test/classicsJava.test.ts).
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
// Types into FILIUS' own command line window (GUIApplicationTerminalWindow on
|
||||
// a GUIDesktopPanel) of a computer without a network, key by key as its key
|
||||
// listener hears them, and writes after each key what the window shows: its
|
||||
// text, its prompt and whether the prompt is shown, the input line. Then the
|
||||
// simulation is stopped and started again (beenden, starten), as a change of
|
||||
// mode does, and the window and the working directory are read again: the
|
||||
// reference for the editor's command line window (packages/sim/test/
|
||||
// terminalWindow.test.ts).
|
||||
//
|
||||
// Usage: java -cp filius.jar:lib/*:classes TerminalWindowRun <output json>
|
||||
|
||||
import filius.gui.anwendungssicht.GUIApplicationTerminalWindow;
|
||||
import filius.gui.anwendungssicht.GUIDesktopPanel;
|
||||
import filius.hardware.knoten.Rechner;
|
||||
import filius.software.system.Betriebssystem;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.io.FileOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.JTextField;
|
||||
|
||||
public class TerminalWindowRun {
|
||||
static final String TERMINAL = "filius.software.lokal.Terminal";
|
||||
|
||||
static Object field(Object owner, String name) throws Exception {
|
||||
Field field = GUIApplicationTerminalWindow.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return field.get(owner);
|
||||
}
|
||||
|
||||
static Map<String, Object> shown(GUIApplicationTerminalWindow window, String step) throws Exception {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("step", step);
|
||||
result.put("text", ((JTextArea) field(window, "terminalField")).getText());
|
||||
JLabel label = (JLabel) field(window, "inputLabel");
|
||||
result.put("prompt", label.getText());
|
||||
result.put("promptShown", label.isVisible());
|
||||
result.put("input", ((JTextField) field(window, "inputField")).getText());
|
||||
return result;
|
||||
}
|
||||
|
||||
static void press(JTextField input, int key, int modifiers) {
|
||||
KeyEvent event = new KeyEvent(input, KeyEvent.KEY_PRESSED, System.currentTimeMillis(), modifiers, key, KeyEvent.CHAR_UNDEFINED);
|
||||
for (KeyListener listener : input.getKeyListeners()) listener.keyPressed(event);
|
||||
}
|
||||
|
||||
/** Waits until the program has answered: the prompt is shown again (or a second passed). */
|
||||
static void settle(GUIApplicationTerminalWindow window) throws Exception {
|
||||
JLabel label = (JLabel) field(window, "inputLabel");
|
||||
for (int waited = 0; waited < 2000 && !label.isVisible(); waited += 20) Thread.sleep(20);
|
||||
Thread.sleep(200);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try {
|
||||
run(args);
|
||||
} catch (Throwable error) {
|
||||
error.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
static void run(String[] args) throws Exception {
|
||||
Rechner host = new Rechner();
|
||||
Betriebssystem system = (Betriebssystem) host.getSystemSoftware();
|
||||
system.installApp(TERMINAL, true);
|
||||
system.starten();
|
||||
GUIDesktopPanel desktop = new GUIDesktopPanel(system);
|
||||
GUIApplicationTerminalWindow window = (GUIApplicationTerminalWindow) desktop.starteAnwendung(TERMINAL);
|
||||
JTextField input = (JTextField) field(window, "inputField");
|
||||
List<Object> steps = new ArrayList<>();
|
||||
steps.add(shown(window, "open"));
|
||||
// A line typed and entered ("type:<line>"), a key ("up", "down", "ctrl+c"), a stop and start ("restart").
|
||||
String[] keys = { "enter:", "enter: ", "enter:pwd", "enter:mkdir dir", "enter:cd dir", "enter:pwd", "enter:nosuchcommand", "up", "up", "up", "down", "down", "down",
|
||||
"enter:cls", "enter:ls", "restart", "enter:pwd", "up", "enter:exit" };
|
||||
for (String key : keys) {
|
||||
if (key.startsWith("enter:")) {
|
||||
input.setText(key.substring("enter:".length()));
|
||||
press(input, KeyEvent.VK_ENTER, 0);
|
||||
settle(window);
|
||||
} else if (key.equals("up")) {
|
||||
press(input, KeyEvent.VK_UP, 0);
|
||||
} else if (key.equals("down")) {
|
||||
press(input, KeyEvent.VK_DOWN, 0);
|
||||
} else if (key.equals("restart")) {
|
||||
system.beenden();
|
||||
system.starten();
|
||||
Thread.sleep(300);
|
||||
}
|
||||
steps.add(shown(window, key));
|
||||
}
|
||||
Map<String, Object> output = new LinkedHashMap<>();
|
||||
output.put("steps", steps);
|
||||
try (FileOutputStream out = new FileOutputStream(args[0])) {
|
||||
out.write((FlsOracle.Json.write(output, "") + "\n").getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
System.out.println("terminal window steps: " + steps.size());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user