Phase 6: Plattformprüfungen und absolute RGB-Farben, Change 05 archivieren

This commit is contained in:
2026-09-07 21:12:25 +02:00
parent 5aa920b768
commit 796564795a
55 changed files with 3772 additions and 200 deletions

View File

@@ -0,0 +1,7 @@
Compiling tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Compiling tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Compiling tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Compiling tb-runner v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-runner)
Compiling tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Compiling tb-cli v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-cli)
Finished `release` profile [optimized] target(s) in 32.49s

View File

@@ -0,0 +1,5 @@
Checking tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Checking tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Checking tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Checking tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.81s

View File

@@ -0,0 +1 @@
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s

View File

@@ -0,0 +1,3 @@
PASS {'shell_exit': 7}
PASS {'abort': True}
PASS {'file_shell': True}

View File

@@ -0,0 +1 @@
{"target": "aarch64-apple-darwin", "tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df", "native_execution": true, "source_free": true, "run_reset": true, "output": " 1 \n 7 3 9 1 \nTB \n"}

View File

@@ -0,0 +1,13 @@
PASS native console
PASS native restart
PASS native stop
PASS native error
PASS native project
PASS native form
PASS native external
PASS PTY Eingabe, Abbruch und Wiederherstellung (CLI/native)
PASS Exportabbruch SIGINT/SIGTERM, Original und Bereinigung
PASS corrupted length
PASS corrupted version
PASS corrupted truncated
Native Abnahme bestanden: aarch64-apple-darwin

View File

@@ -0,0 +1,3 @@
PASS {'shell_exit': 7}
PASS {'abort': True}
PASS {'file_shell': True}

View File

@@ -0,0 +1,44 @@
{
"scope": "automated-target-checks",
"started_utc": "2026-09-07T19:04:25.039868+00:00",
"host_target": "aarch64-apple-darwin",
"os_version": "macOS-26.6.2-arm64-arm-64bit-Mach-O",
"python_version": "3.14.7",
"checkout_revision": "5aa920b768a2ad29ed17c5da137e54675a7292d6",
"working_tree_dirty": true,
"working_tree_diff_sha256": "0834248d5bd0643cf959a7e6d78f74b4401d541cf65bb7226026b7eac13a136c",
"program_sha256": {
"tb": "7472ced2e905da8e8389bb9dafb221e39d2cd465110e982328e7076dc18817a9",
"tbc": "52491c691507098f60524bbc078ef8e53a1883d284803427c422db42d95aa469",
"tbrt": "4095b5412cf7c0470068c3c0b7f5ab325d0d357cd8b8fe8bbb966fdef1898df0",
"tb-template": "a9d73ea8002b8f01bbb1fdc033a2595605ceb3b8c55d4531bf00da236351ccbf"
},
"tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df",
"manual_terminal_status": "not_run",
"matrix_complete": false,
"checks": {
"library": {
"status": "passed",
"exit_code": 0
},
"native": {
"status": "passed",
"exit_code": 0
},
"ide-pty": {
"status": "passed",
"exit_code": 0
},
"artifact-stability": {
"status": "passed"
}
},
"harness_sha256": {
"library-abnahme.py": "118de32bc5a9cbb254b234b420455f75961603afb16aa94333be8c4b5b17e962",
"platform-abnahme.py": "a5eadbd4bcabaae6c3c173ce933f83e331d03b1cf22998fe564519fe8f6b9696",
"native-abnahme.py": "fe9f34bcc581e6230909b6277bae4768dbf6be068e4616b47aaff4390424ca14",
"release-abnahme.py": "f77b1481913050faa321f9978d0f6cf31e8f884a1f626a861727b5393e1bea83",
"ide-execution-pty.py": "fdbde7bf84068fe1ec8cfa037a071644add947d04930b8c73770b59f3070c985"
},
"all_automated_passed": true
}

View File

@@ -0,0 +1,110 @@
- Validating...
✓ spec/bytecode-kompilat
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/datei-eio
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ereignissteuerung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/forms-dateiformat
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/forms-objektmodell
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/forms-steuerelemente
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-ausfuehrung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-debugger
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-editor
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-formulardesigner
✓ spec/ide-hilfe
✓ spec/ide-native-exporte
✓ spec/ide-oberflaeche
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-phasenabnahme
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/ide-projekte
✓ spec/isam-datenbank
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/kompat-testkorpus
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/konsolen-basisbibliothek
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[9]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[11]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/laufzeitfehler
✓ spec/native-executables
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/pcode-bibliotheken
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[1]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ change/phase-6-05-plattformmatrix
✓ change/phase-6-06-gitea-actions-und-releases
✓ change/phase-6-07-dokumentation-und-phasenabnahme
✓ spec/release-kompatibilitaet
✓ spec/sprach-frontend
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[11]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[13]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/sprachinventar
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[2]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/textbildschirm
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[4]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[6]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[8]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[9]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[10]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/vm-ausfuehrung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[3]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[5]: Requirement text is very long (>500 characters). Consider breaking it down.
[INFO] requirements[7]: Requirement text is very long (>500 characters). Consider breaking it down.
✓ spec/vm-fehlerbehandlung
[INFO] requirements[0]: Requirement text is very long (>500 characters). Consider breaking it down.
Totals: 30 passed, 0 failed (30 items)

View File

@@ -0,0 +1,18 @@
M crates/tb-ide/src/render.rs
M crates/tb-ide/tests/app.rs
M crates/tb-ide/tests/execution.rs
M crates/tb-ui/examples/spike.rs
M crates/tb-ui/src/screen.rs
M crates/tb-ui/src/terminal.rs
M docs/ide-referenz.md
M openspec/changes/phase-6-05-plattformmatrix/design.md
M openspec/changes/phase-6-05-plattformmatrix/proposal.md
M openspec/changes/phase-6-05-plattformmatrix/tasks.md
M tests/support/ide-execution-pty.py
?? docs/plattformmatrix.md
?? openspec/changes/phase-6-05-plattformmatrix/evidence/
?? openspec/changes/phase-6-05-plattformmatrix/specs/ide-oberflaeche/
?? openspec/changes/phase-6-05-plattformmatrix/specs/textbildschirm/
?? openspec/changes/phase-6-05-plattformmatrix/verification.md
?? tests/platform/
?? tests/support/platform-abnahme.py

View File

@@ -0,0 +1,12 @@
Compiling tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Finished `test` profile [unoptimized + debuginfo] target(s) in 1.46s
Running tests/app.rs (target/debug/deps/app-d3d6e33413d63d0f)
running 4 tests
test theme_color_commands_use_explicit_values_and_never_modify_terminal_palette ... ok
test theme_windows_erase_desktop_and_underlying_windows_on_every_redraw ... ok
test theme_designer_chrome_and_handles_preserve_program_palette ... ok
test theme_contrast_covers_rendered_states_and_rejects_bad_pairs ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 16 filtered out; finished in 0.42s

View File

@@ -0,0 +1,5 @@
Checking tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Checking tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Checking tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Checking tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.64s

View File

@@ -0,0 +1,19 @@
M crates/tb-ide/src/render.rs
M crates/tb-ide/tests/app.rs
M crates/tb-ide/tests/execution.rs
M crates/tb-ui/examples/spike.rs
M crates/tb-ui/src/screen.rs
M crates/tb-ui/src/terminal.rs
M docs/ide-referenz.md
M openspec/changes/phase-6-05-plattformmatrix/design.md
M openspec/changes/phase-6-05-plattformmatrix/proposal.md
M openspec/changes/phase-6-05-plattformmatrix/tasks.md
M tests/support/ide-execution-pty.py
?? docs/plattformmatrix.md
?? openspec/changes/phase-6-05-plattformmatrix/evidence/
?? openspec/changes/phase-6-05-plattformmatrix/specs/ide-oberflaeche/
?? openspec/changes/phase-6-05-plattformmatrix/specs/textbildschirm/
?? openspec/changes/phase-6-05-plattformmatrix/verification.md
?? tests/platform/
?? tests/support/__pycache__/
?? tests/support/platform-abnahme.py

View File

@@ -0,0 +1,943 @@
diff --git a/crates/tb-ide/src/render.rs b/crates/tb-ide/src/render.rs
index 9c64b24..1ab5a4b 100644
--- a/crates/tb-ide/src/render.rs
+++ b/crates/tb-ide/src/render.rs
@@ -11,74 +11,9 @@ use ratatui::{
};
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
-/// DOS-Farben der IDE. Die ersten 16 ANSI-Einträge gehören dem Terminalprofil.
-/// BASIC-Ausgabe verwendet weiterhin ihre eigene Farbabbildung in tb-ui.
+/// Feste DOS-RGB-Farben, identisch für IDE und BASIC-Ausgabe.
pub fn dos(n: u8) -> Color {
- static COLORS: std::sync::OnceLock<u16> = std::sync::OnceLock::new();
- dos_color(
- n,
- *COLORS.get_or_init(|| {
- let detected = crossterm::style::available_color_count();
- // Ein generisches COLORTERM (z.B. "yes") verdeckt bei crossterm TERM.
- if detected < 256 && std::env::var("TERM").is_ok_and(|s| s.contains("256")) {
- 256
- } else {
- detected
- }
- }),
- )
-}
-
-pub fn dos_color(n: u8, colors: u16) -> Color {
- let n = n.min(15) as usize;
- const RGB: [(u8, u8, u8); 16] = [
- (0, 0, 0),
- (0, 0, 170),
- (0, 170, 0),
- (0, 170, 170),
- (170, 0, 0),
- (170, 0, 170),
- (170, 85, 0),
- (170, 170, 170),
- (85, 85, 85),
- (85, 85, 255),
- (85, 255, 85),
- (85, 255, 255),
- (255, 85, 85),
- (255, 85, 255),
- (255, 255, 85),
- (255, 255, 255),
- ];
- if colors > 256 {
- let (r, g, b) = RGB[n];
- Color::Rgb(r, g, b)
- } else if colors >= 256 {
- // Fester 6x6x6-Farbwürfel / Graurampe statt profilabhängiger Systemfarben.
- Color::Indexed(
- [
- 16, 19, 34, 37, 124, 127, 130, 248, 240, 63, 83, 87, 203, 207, 227, 231,
- ][n],
- )
- } else {
- [
- Color::Black,
- Color::Blue,
- Color::Green,
- Color::Cyan,
- Color::Red,
- Color::Magenta,
- Color::Yellow,
- Color::Gray,
- Color::DarkGray,
- Color::LightBlue,
- Color::LightGreen,
- Color::LightCyan,
- Color::LightRed,
- Color::LightMagenta,
- Color::LightYellow,
- Color::White,
- ][n]
- }
+ tb_ui::screen::basic_color(n.min(15))
}
pub(crate) fn pair(fg: u8, bg: u8) -> Style {
@@ -160,6 +95,8 @@ impl App {
5
},
);
+ // Block/Paragraph ändern Stile, löschen aber keine vorhandenen Zeichen.
+ f.render_widget(Clear, rect);
f.render_widget(
Block::default()
.borders(Borders::ALL)
diff --git a/crates/tb-ide/tests/app.rs b/crates/tb-ide/tests/app.rs
index 6ef2fdd..8ccd8c9 100644
--- a/crates/tb-ide/tests/app.rs
+++ b/crates/tb-ide/tests/app.rs
@@ -930,21 +930,7 @@ fn theme_rgb(color: ratatui::style::Color) -> (u8, u8, u8) {
use ratatui::style::Color;
match color {
Color::Rgb(r, g, b) => (r, g, b),
- Color::Indexed(n @ 16..=231) => {
- let n = n - 16;
- let channel = |v: u8| if v == 0 { 0 } else { 55 + v * 40 };
- (channel(n / 36), channel(n / 6 % 6), channel(n % 6))
- }
- Color::Indexed(n @ 232..=255) => {
- let v = 8 + (n - 232) * 10;
- (v, v, v)
- }
- _ => {
- let n = (0..16)
- .find(|n| tb_ide::render::dos_color(*n, 16) == color)
- .unwrap_or_else(|| panic!("Unbestimmte IDE-Farbe: {color:?}"));
- theme_rgb(tb_ide::render::dos_color(n, u16::MAX))
- }
+ _ => panic!("Keine absolute RGB-Farbe: {color:?}"),
}
}
fn theme_contrast(fg: ratatui::style::Color, bg: ratatui::style::Color) -> f64 {
@@ -983,23 +969,21 @@ fn assert_theme(app: &mut App, context: &str) {
#[test]
fn theme_contrast_covers_rendered_states_and_rejects_bad_pairs() {
- use tb_ide::render::dos_color;
- for colors in [256, u16::MAX] {
- for (fg, bg) in tb_ide::options::Options::default().colors {
- assert!(theme_contrast(dos_color(fg, colors), dos_color(bg, colors)) >= 4.5);
- }
- assert!(theme_contrast(dos_color(15, colors), dos_color(7, colors)) < 4.5);
- for n in 0..16 {
- let fg = if matches!(n, 0 | 1 | 4 | 5 | 6 | 8 | 9) {
- 15
- } else {
- 0
- };
- assert!(
- theme_contrast(dos_color(fg, colors), dos_color(n, colors)) >= 4.5,
- "Palettenbeschriftung {n}"
- );
- }
+ use tb_ide::render::dos;
+ for (fg, bg) in tb_ide::options::Options::default().colors {
+ assert!(theme_contrast(dos(fg), dos(bg)) >= 4.5);
+ }
+ assert!(theme_contrast(dos(15), dos(7)) < 4.5);
+ for n in 0..16 {
+ let fg = if matches!(n, 0 | 1 | 4 | 5 | 6 | 8 | 9) {
+ 15
+ } else {
+ 0
+ };
+ assert!(
+ theme_contrast(dos(fg), dos(n)) >= 4.5,
+ "Palettenbeschriftung {n}"
+ );
}
let t = Temp::new();
let mut app = t.app();
@@ -1086,34 +1070,23 @@ fn theme_color_commands_use_explicit_values_and_never_modify_terminal_palette()
buffer::Cell,
style::Color,
};
- use tb_ide::render::dos_color;
- assert_eq!(dos_color(1, u16::MAX), Color::Rgb(0, 0, 170));
- assert_eq!(dos_color(5, u16::MAX), Color::Rgb(170, 0, 170));
- assert_eq!(dos_color(6, u16::MAX), Color::Rgb(170, 85, 0));
- for colors in [16, 256, u16::MAX] {
- for n in 0..16 {
- let color = dos_color(n, colors);
- if let Color::Indexed(index) = color {
- assert!(index >= 16);
- }
- let mut cell = Cell::default();
- cell.set_symbol("X")
- .set_bg(color)
- .set_fg(dos_color(15, colors));
- let mut output = Vec::new();
- CrosstermBackend::new(&mut output)
- .draw(std::iter::once((0, 0, &cell)))
- .unwrap();
- let sequence = String::from_utf8(output).unwrap();
- assert!(sequence.contains('X'));
- if colors >= 256 {
- assert!(sequence.contains("48;"));
- }
- if colors > 256 {
- assert!(sequence.contains("48;2;"));
- }
- assert!(!sequence.contains("\x1b]")); // Keine globale Palettenänderung.
- }
+ use tb_ide::render::dos;
+ assert_eq!(dos(1), Color::Rgb(0, 0, 170));
+ assert_eq!(dos(5), Color::Rgb(170, 0, 170));
+ assert_eq!(dos(6), Color::Rgb(170, 85, 0));
+ for n in 0..16 {
+ assert_eq!(dos(n), tb_ui::screen::basic_color(n));
+ let mut cell = Cell::default();
+ cell.set_symbol("X").set_bg(dos(n)).set_fg(dos(15));
+ let mut output = Vec::new();
+ CrosstermBackend::new(&mut output)
+ .draw(std::iter::once((0, 0, &cell)))
+ .unwrap();
+ let sequence = String::from_utf8(output).unwrap();
+ assert!(sequence.contains('X'));
+ assert!(sequence.contains("38;2;255;255;255"));
+ assert!(sequence.contains("48;2;"));
+ assert!(!sequence.contains("\x1b]")); // Keine globale Palettenänderung.
}
}
@@ -1149,3 +1122,61 @@ fn theme_designer_chrome_and_handles_preserve_program_palette() {
assert_theme(&mut app, "Designer-Werkzeug");
}
}
+
+#[test]
+fn theme_windows_erase_desktop_and_underlying_windows_on_every_redraw() {
+ use ratatui::{
+ layout::Rect,
+ style::{Color, Modifier},
+ };
+ let t = Temp::new();
+ let mut app = t.app();
+ app.options.syntax_checking = false;
+ // Ein sichtbares Zeichen macht das Durchscheinen sicher erkennbar.
+ app.options.desktop = '@';
+ type_text(&mut app, "PRINT 42");
+ let mut term = Terminal::new(TestBackend::new(app.size.0, app.size.1)).unwrap();
+ for selected in [false, true, false] {
+ if selected {
+ key(&mut app, K::Char('a'), M::CONTROL);
+ } else {
+ plain(&mut app, K::Right);
+ }
+ term.draw(|f| app.render(f)).unwrap();
+ let b = term.backend().buffer();
+ let rect = app.rect(app.windows.iter().find(|w| w.id == app.active).unwrap());
+ let inner = Rect::new(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2);
+ for y in inner.y..inner.bottom() {
+ for x in inner.x..inner.right() {
+ let c = &b[(x, y)];
+ assert_ne!(c.symbol(), "@", "Desktop in Fenster bei {x}/{y}");
+ assert_eq!(c.bg, Color::Rgb(0, 0, 170));
+ if y > inner.y {
+ assert_eq!(c.symbol(), " ", "Restinhalt bei {x}/{y}");
+ assert!(!c.modifier.contains(Modifier::REVERSED));
+ }
+ }
+ }
+ assert_eq!(b[(inner.x, inner.y)].symbol(), "P");
+ assert_eq!(
+ b[(inner.x, inner.y)].modifier.contains(Modifier::REVERSED),
+ selected
+ );
+ }
+ // Beim Aktivieren eines überlappenden Fensters müssen auch fremde Texte weg.
+ app.execute(Command::NewModule);
+ plain(&mut app, K::Enter);
+ assert!(app.dialog.is_none());
+ term.draw(|f| app.render(f)).unwrap();
+ let b = term.backend().buffer();
+ let rect = app.rect(app.windows.iter().find(|w| w.id == app.active).unwrap());
+ for y in rect.y + 1..rect.bottom() - 1 {
+ for x in rect.x + 1..rect.right() - 1 {
+ assert_eq!(
+ b[(x, y)].symbol(),
+ " ",
+ "Überdeckter Fensterinhalt bei {x}/{y}"
+ );
+ }
+ }
+}
diff --git a/crates/tb-ide/tests/execution.rs b/crates/tb-ide/tests/execution.rs
index 237ac71..e92a0b6 100644
--- a/crates/tb-ide/tests/execution.rs
+++ b/crates/tb-ide/tests/execution.rs
@@ -530,3 +530,35 @@ fn comment_only_source_change_also_requires_revision_choice() {
DialogKind::ResumeRevision
));
}
+
+#[test]
+fn platform_probe_records_each_basic_key_once_and_keeps_ide_keys_out() {
+ let t = Temp::new();
+ let log = t.0.join("platform-events.log");
+ let source = include_str!("../../../tests/platform/input.frm")
+ .replace("platform-events.log", &log.to_string_lossy());
+ let path = t.0.join("input.frm");
+ fs::write(&path, source).unwrap();
+ let mut a = t.app("");
+ a.load_initial_project(path).unwrap();
+ a.execute(Command::Start);
+ tick(&mut a);
+ key(&mut a, K::Char('a'), M::NONE);
+ tick(&mut a);
+ a.handle(Event::Key(KeyEvent::new_with_kind(
+ K::Char('a'),
+ M::NONE,
+ crossterm::event::KeyEventKind::Release,
+ )));
+ tick(&mut a);
+ let events = fs::read_to_string(&log).unwrap();
+ assert_eq!(
+ events.replace('\r', ""),
+ "Down: 97 : 0 \nPress: 97 : 0 \nUp: 97 : 0 \n"
+ );
+ edit_window(&mut a);
+ key(&mut a, K::F(1), M::NONE);
+ tick(&mut a);
+ assert_eq!(fs::read_to_string(&log).unwrap(), events);
+ assert!(matches!(a.active_window().unwrap().kind, WindowKind::Help));
+}
diff --git a/crates/tb-ui/examples/spike.rs b/crates/tb-ui/examples/spike.rs
index b6b5ff3..38196ed 100644
--- a/crates/tb-ui/examples/spike.rs
+++ b/crates/tb-ui/examples/spike.rs
@@ -1,15 +1,12 @@
//! Phase-0-Spike: 80×25-Bildschirmpuffer, 16-Farben-Palette, Unicode,
//! Tastatur- und Mausereignisse.
//!
-//! Start: `cargo run -p tb-ui --example spike` — Beenden mit Esc.
+//! Start: `cargo run -p tb-ui --features terminal --example spike -- --log events.txt` — Beenden mit Esc.
//! Getippte Zeichen erscheinen im Eingabebereich; Sondertasten und
//! Mausereignisse werden in der Statuszeile angezeigt.
-use crossterm::event::{
- self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
-};
-use crossterm::execute;
-use std::io::stdout;
+use crossterm::event::{self, Event, KeyCode, KeyEventKind};
+use std::io::{stdout, Write};
use std::time::Duration;
use tb_ui::screen::{ScreenWidget, TextScreen};
@@ -57,8 +54,24 @@ fn status(s: &mut TextScreen, text: &str) {
}
fn main() -> anyhow::Result<()> {
- let mut terminal = ratatui::init();
- execute!(stdout(), EnableMouseCapture)?;
+ let args: Vec<_> = std::env::args_os().skip(1).collect();
+ anyhow::ensure!(
+ args.is_empty() || (args.len() == 2 && args[0] == "--log"),
+ "Aufruf: spike [--log neue-datei]"
+ );
+ let mut log = if args.len() == 2 {
+ Some(
+ std::fs::OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&args[1])?,
+ )
+ } else {
+ None
+ };
+ let _guard = tb_ui::terminal::TerminalGuard::enter(stdout())?;
+ let mut terminal = ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?;
+ let mut sequence = 0u64;
let (tw, th) = crossterm::terminal::size()?;
let mut screen = TextScreen::with_size(tw as usize, th as usize);
@@ -70,7 +83,12 @@ fn main() -> anyhow::Result<()> {
if !event::poll(Duration::from_millis(100))? {
continue;
}
- match event::read()? {
+ let received = event::read()?;
+ sequence += 1;
+ if let Some(log) = &mut log {
+ writeln!(log, "{sequence}: {received:?}")?;
+ }
+ match received {
Event::Key(k) if k.kind != KeyEventKind::Release => match k.code {
KeyCode::Esc => break,
KeyCode::Char(c) => {
@@ -105,7 +123,5 @@ fn main() -> anyhow::Result<()> {
}
}
- execute!(stdout(), DisableMouseCapture)?;
- ratatui::restore();
Ok(())
}
diff --git a/crates/tb-ui/src/screen.rs b/crates/tb-ui/src/screen.rs
index 8727b91..8297812 100644
--- a/crates/tb-ui/src/screen.rs
+++ b/crates/tb-ui/src/screen.rs
@@ -12,11 +12,28 @@ use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::widgets::Widget;
-/// Abbildung der klassischen 16-Farben-Palette auf ANSI-Indexfarben.
-/// (Klassisch: 1 = Blau, 4 = Rot — ANSI: 1 = Rot, 4 = Blau usw.)
+/// Klassische DOS-Farbnummern als absolute RGB-Werte, ohne Terminalprofil.
pub fn basic_color(n: u8) -> Color {
- const MAP: [u8; 16] = [0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15];
- Color::Indexed(MAP[(n & 0x0F) as usize])
+ const RGB: [(u8, u8, u8); 16] = [
+ (0, 0, 0),
+ (0, 0, 170),
+ (0, 170, 0),
+ (0, 170, 170),
+ (170, 0, 0),
+ (170, 0, 170),
+ (170, 85, 0),
+ (170, 170, 170),
+ (85, 85, 85),
+ (85, 85, 255),
+ (85, 255, 85),
+ (85, 255, 255),
+ (255, 85, 85),
+ (255, 85, 255),
+ (255, 255, 85),
+ (255, 255, 255),
+ ];
+ let (r, g, b) = RGB[(n & 0x0F) as usize];
+ Color::Rgb(r, g, b)
}
/// Zeichenbares Gegenstück zu [`TextScreen`].
@@ -66,9 +83,9 @@ mod tests {
#[test]
fn farbabbildung() {
- assert_eq!(basic_color(1), Color::Indexed(4)); // klassisch Blau
- assert_eq!(basic_color(4), Color::Indexed(1)); // klassisch Rot
- assert_eq!(basic_color(14), Color::Indexed(11)); // Gelb
- assert_eq!(basic_color(15), Color::Indexed(15)); // Weiß
+ assert_eq!(basic_color(1), Color::Rgb(0, 0, 170)); // klassisch Blau
+ assert_eq!(basic_color(4), Color::Rgb(170, 0, 0)); // klassisch Rot
+ assert_eq!(basic_color(14), Color::Rgb(255, 255, 85)); // Gelb
+ assert_eq!(basic_color(15), Color::Rgb(255, 255, 255)); // Weiß
}
}
diff --git a/crates/tb-ui/src/terminal.rs b/crates/tb-ui/src/terminal.rs
index 77493b2..67a6c8d 100644
--- a/crates/tb-ui/src/terminal.rs
+++ b/crates/tb-ui/src/terminal.rs
@@ -11,23 +11,47 @@ use std::io::{self, Write};
pub struct TerminalGuard<W: Write> {
writer: W,
raw: fn(bool) -> io::Result<()>,
+ #[cfg(windows)]
+ modes: Option<ConsoleModes>,
}
impl<W: Write> TerminalGuard<W> {
pub fn enter(writer: W) -> io::Result<Self> {
- Self::with_raw(writer, |on| {
+ // Farben tragen UI-/BASIC-Zustand und müssen auch ohne Farberkennung
+ // oder mit NO_COLOR als explizite RGB-Werte ausgegeben werden.
+ crossterm::style::force_color_output(true);
+ #[cfg(windows)]
+ let modes = ConsoleModes::capture()?;
+ #[cfg(windows)]
+ modes.enable_output()?;
+ #[allow(unused_mut)]
+ let mut guard = Self::with_raw(writer, |on| {
if on {
enable_raw_mode()
} else {
disable_raw_mode()
}
- })
+ })?;
+ #[cfg(windows)]
+ {
+ guard.modes = Some(modes);
+ }
+ Ok(guard)
}
fn with_raw(writer: W, raw: fn(bool) -> io::Result<()>) -> io::Result<Self> {
- let mut guard = Self { writer, raw };
+ let mut guard = Self {
+ writer,
+ raw,
+ #[cfg(windows)]
+ modes: None,
+ };
guard.resume()?;
Ok(guard)
}
fn resume(&mut self) -> io::Result<()> {
+ #[cfg(windows)]
+ if let Some(modes) = &self.modes {
+ modes.enable_output()?;
+ }
(self.raw)(true)?;
execute!(
self.writer,
@@ -38,15 +62,18 @@ impl<W: Write> TerminalGuard<W> {
)
}
fn suspend(&mut self) -> io::Result<()> {
- (self.raw)(false)?;
- execute!(
+ let raw = (self.raw)(false);
+ let output = execute!(
self.writer,
ResetColor,
Show,
DisableMouseCapture,
DisableBracketedPaste,
LeaveAlternateScreen
- )
+ );
+ #[cfg(windows)]
+ let output = output.and(self.modes.as_ref().map_or(Ok(()), ConsoleModes::restore));
+ raw.and(output)
}
/// Foreground child owns cooked terminal input. Always restore the IDE,
/// including spawn/wait errors and a child interrupted by Ctrl+C.
@@ -91,6 +118,10 @@ impl<W: Write> TerminalGuard<W> {
}
impl<W: Write> Drop for TerminalGuard<W> {
fn drop(&mut self) {
+ #[cfg(windows)]
+ if let Some(modes) = &self.modes {
+ let _ = modes.enable_output();
+ }
let _ = (self.raw)(false);
let _ = execute!(
self.writer,
@@ -103,6 +134,58 @@ impl<W: Write> Drop for TerminalGuard<W> {
}
}
+// Crossterm stellt unter Windows Raw-/Mausflags pauschal zurück. Der Besitzer
+// muss zusätzlich die tatsächlichen Eingabe- und Ausgabemodi wiederherstellen.
+#[cfg(windows)]
+#[derive(Debug)]
+struct ConsoleModes([(std::os::windows::io::RawHandle, u32); 2]);
+#[cfg(windows)]
+#[link(name = "kernel32")]
+extern "system" {
+ fn GetStdHandle(kind: u32) -> std::os::windows::io::RawHandle;
+ fn GetConsoleMode(handle: std::os::windows::io::RawHandle, mode: *mut u32) -> i32;
+ fn SetConsoleMode(handle: std::os::windows::io::RawHandle, mode: u32) -> i32;
+}
+#[cfg(windows)]
+impl ConsoleModes {
+ fn capture() -> io::Result<Self> {
+ let mut modes = [(std::ptr::null_mut(), 0); 2];
+ for (slot, kind) in modes.iter_mut().zip([(-10i32) as u32, (-11i32) as u32]) {
+ // Borrowed standard handles: valid until the terminal owner exits.
+ slot.0 = unsafe { GetStdHandle(kind) };
+ if unsafe { GetConsoleMode(slot.0, &mut slot.1) } == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ Ok(Self(modes))
+ }
+ fn enable_output(&self) -> io::Result<()> {
+ // Crossterm caches ANSI support, but a Shell handoff restores the old
+ // output flags. Re-enable VT for each ownership cycle, including re-entry.
+ if crossterm::ansi_support::supports_ansi()
+ && unsafe { SetConsoleMode(self.0[1].0, self.0[1].1 | 0x0004) } == 0
+ {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(())
+ }
+ fn restore(&self) -> io::Result<()> {
+ let mut result = Ok(());
+ for (handle, mode) in self.0 {
+ if unsafe { SetConsoleMode(handle, mode) } == 0 && result.is_ok() {
+ result = Err(io::Error::last_os_error());
+ }
+ }
+ result
+ }
+}
+#[cfg(windows)]
+impl Drop for ConsoleModes {
+ fn drop(&mut self) {
+ let _ = self.restore();
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -149,7 +232,7 @@ mod tests {
("exit 7", false, 7),
(
if cfg!(windows) {
- "ping -n 30 127.0.0.1 >nul"
+ "for /L %i in (1,1,1000000000) do @rem"
} else {
"exec sleep 30"
},
@@ -160,10 +243,13 @@ mod tests {
let out = Output(Rc::default());
let copy = out.clone();
let mut guard = TerminalGuard::with_raw(out, raw).unwrap();
- assert_eq!(
- guard.shell_with_interrupt(command, || abort).unwrap(),
- expected
- );
+ let started = std::time::Instant::now();
+ let code = guard.shell_with_interrupt(command, || abort).unwrap();
+ if !abort {
+ assert_eq!(code, expected);
+ }
+ // Windows TerminateProcess liefert einen Code; Unix ein Signal.
+ assert!(started.elapsed() < std::time::Duration::from_secs(5));
assert_eq!(RAW.load(Ordering::SeqCst), 1);
let text = String::from_utf8(copy.0.borrow().clone()).unwrap();
assert_eq!(text.matches("?1049h").count(), 2);
@@ -187,4 +273,111 @@ mod tests {
.unwrap()
.contains("?25h"));
}
+ /// Run explicitly on Windows; never counts as a Windows Terminal UI test.
+ #[cfg(windows)]
+ #[test]
+ #[ignore = "benötigt Windows mit echter Konsole; isolierter Kindprozess"]
+ fn windows_console_restores_modes() {
+ use std::os::windows::{io::AsRawHandle, process::CommandExt};
+ use std::process::{Command, Stdio};
+ use std::time::{Duration, Instant};
+ const CHILD: &str = "TB_CONSOLE_TEST_CHILD";
+ if std::env::var_os(CHILD).is_none() {
+ let mut child = Command::new(std::env::current_exe().unwrap())
+ .args([
+ "--exact",
+ "terminal::tests::windows_console_restores_modes",
+ "--ignored",
+ "--nocapture",
+ ])
+ .env(CHILD, "1")
+ .creation_flags(0x00000010) // CREATE_NEW_CONSOLE: never touches the caller's console.
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .spawn()
+ .unwrap();
+ let deadline = Instant::now() + Duration::from_secs(30);
+ loop {
+ if let Some(status) = child.try_wait().unwrap() {
+ assert!(
+ status.success(),
+ "Isolierte Windows-Konsolenprüfung: {status}"
+ );
+ return;
+ }
+ if Instant::now() >= deadline {
+ let _ = child.kill();
+ let _ = child.wait();
+ panic!("Windows-Konsolenprüfung hat die Frist überschritten");
+ }
+ std::thread::sleep(Duration::from_millis(20));
+ }
+ }
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn SetStdHandle(kind: u32, handle: std::os::windows::io::RawHandle) -> i32;
+ }
+ let open = |name| {
+ std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(name)
+ .unwrap()
+ };
+ let input = open("CONIN$");
+ let output = open("CONOUT$");
+ // Standard streams started as NUL; bind to this child's own new console.
+ assert_ne!(
+ unsafe { SetStdHandle((-10i32) as u32, input.as_raw_handle()) },
+ 0
+ );
+ assert_ne!(
+ unsafe { SetStdHandle((-11i32) as u32, output.as_raw_handle()) },
+ 0
+ );
+ let original = ConsoleModes::capture().unwrap();
+ // Non-default echo flags catch a reset-to-default masquerading as restore.
+ let mode = original.0[0].1 & !0x0004; // ENABLE_ECHO_INPUT
+ assert_ne!(unsafe { SetConsoleMode(input.as_raw_handle(), mode) }, 0);
+ let expected = ConsoleModes::capture().unwrap();
+ let assert_restored = || assert_eq!(ConsoleModes::capture().unwrap().0, expected.0);
+ for (command, abort, code) in [
+ ("exit 0", false, 0),
+ ("exit 7", false, 7),
+ ("for /L %i in (1,1,1000000000) do @rem", true, 0),
+ ] {
+ let mut guard = TerminalGuard::enter(io::stdout()).unwrap();
+ guard.suspend().unwrap();
+ assert_restored();
+ guard.resume().unwrap();
+ if crossterm::ansi_support::supports_ansi() {
+ assert_ne!(ConsoleModes::capture().unwrap().0[1].1 & 0x0004, 0);
+ }
+ let result = guard.shell_with_interrupt(command, || abort).unwrap();
+ if !abort {
+ assert_eq!(result, code);
+ }
+ assert!(crossterm::terminal::is_raw_mode_enabled().unwrap());
+ drop(guard);
+ assert_restored();
+ }
+ let error = (|| -> io::Result<()> {
+ let _guard = TerminalGuard::enter(io::stdout())?;
+ Err(io::Error::other("Erzwungener Renderfehler"))
+ })();
+ assert!(error.is_err());
+ assert_restored();
+ struct Broken;
+ impl Write for Broken {
+ fn write(&mut self, _: &[u8]) -> io::Result<usize> {
+ Err(io::Error::other("Initialisierung"))
+ }
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+ assert!(TerminalGuard::enter(Broken).is_err());
+ assert_restored();
+ }
}
diff --git a/docs/ide-referenz.md b/docs/ide-referenz.md
index 6cfa05d..6104316 100644
--- a/docs/ide-referenz.md
+++ b/docs/ide-referenz.md
@@ -102,24 +102,23 @@ Laufzeit-Defaults kompilierter Programme weichen ab (Titel Weiß auf Schwarz).
## 4a. Farbabbildung und Kontrast (Phase 6)
-Die DOS-Farbnummern in Options→Display bleiben erhalten. Die IDE verwendet
-bei `COLORTERM=truecolor`/`24bit` explizite RGB-Werte der klassischen
-16-Farben-Palette; bei `TERM=…256color` feste Einträge im Farbwürfel und der
-Graurampe außerhalb der ersten 16 ANSI-Farben. Ein generisches `COLORTERM`
-verdeckt diese 256-Farben-Erkennung nicht. Auf Windows verwendet die IDE
-zusätzlich die ANSI-Fähigkeitserkennung des vorhandenen Terminaladapters.
-Die Erkennung erfolgt einmal pro Prozess; nach Profiländerungen neu starten.
-
-Damit hängt das Standardtheme bei expliziter Farbausgabe nicht von einer
-pastellfarbenen ANSI-Palette ab. Beispielwerte: Codehintergrund `#0000AA`,
-Codeschrift `#AAAAAA`, aktiver Titel `#FFFFFF` auf `#AA00AA`. DOS-Farbe 6
-ist Braun (`#AA5500`), nicht das frei konfigurierte ANSI-Gelb. Der
-256-Farben-Fallback nähert diese Werte an (`#0000AF`, `#A8A8A8`, `#AF00AF`).
-Die globale Terminalpalette wird nicht verändert. Unbekannte beziehungsweise
-reine 16-Farb-Terminals verwenden weiterhin die ANSI-Zuordnung; dort ist ein
-geeignetes Terminalprofil nötig. `NO_COLOR`, Transparenz, Farbfilter und
-Inaktivitätsdimmung des Emulators können die Darstellung zusätzlich verändern;
-die IDE überschreibt solche Benutzereinstellungen nicht.
+Die DOS-Farbnummern in Options→Display bleiben erhalten. IDE, Formularvorschau
+und BASIC-Ausgabe verwenden dieselbe feste klassische RGB-Palette. Jede
+Farbe wird als 24-Bit-Vorder-/Hintergrundwert ausgegeben, unabhängig von
+`TERM`, `COLORTERM`, `NO_COLOR` oder der ANSI-Palette des Terminalprofils.
+Es gibt keinen Rückfall auf Index- oder Profilfarben. Die globale
+Terminalpalette wird nicht verändert; gespeichert werden weiterhin
+DOS-Farbnummern, und BASIC bestimmt seine eigenen ForeColor/BackColor-Werte.
+
+Beispielwerte: Codehintergrund `#0000AA`, Codeschrift `#AAAAAA`, aktiver
+Titel `#FFFFFF` auf `#AA00AA`. DOS-Farbe 6 ist Braun (`#AA5500`).
+Fensterflächen werden vor dem Zeichnen geleert, damit Desktop-Füllzeichen
+und Inhalte verdeckter Fenster keine scheinbar helleren Hintergründe erzeugen.
+
+Die Ausgabe setzt einen Terminalemulator mit 24-Bit-Farbunterstützung voraus.
+Fehlende RGB-Unterstützung bleibt ein offener Zielbefund. Transparenz,
+Farbfilter und Inaktivitätsdimmung sind nachgelagerte Emulatorfunktionen;
+sie werden für die visuelle Farbprüfung getrennt protokolliert.
### Referenzfundstellen
@@ -144,7 +143,7 @@ Palette. Ansichten belegen Flächen und Rollen, keine heutige Terminalausgabe.
| Deaktivierte Beschriftungen | Nicht ausreichend durch diese Ansichten belegt | Lesbares Schwarz auf Grau, Menüs zusätzlich `×` und kursiv |
| Hilfe und Debuggermarkierungen | In diesen Ansichten nicht belegt | Dunkelblaue Hilfetitel/Links auf Grau; Fokus Weiß auf Blau; Breakpoints Weiß auf Rot, Ausführungszeile Schwarz auf Gelb |
-Die Standardpaare müssen bei RGB- und 256-Farben-Ausgabe mindestens 4,5:1
+Die Standardpaare müssen bei RGB-Ausgabe mindestens 4,5:1
Textkontrast erreichen; notwendige nichttextuelle Fokusmarkierungen mindestens
3:1. Die Zelltests prüfen auch Rahmen und Handles auf mindestens 4,5:1
gegen ihren eigenen Hintergrund. Benutzerauswahl in Display bleibt frei;
@@ -154,16 +153,16 @@ Designer-Werkzeuge und Handles die IDE-Farben verwenden.
Die sRGB-Kontrastberechnung ergibt für die wichtigsten Standardpaare:
-| Rolle | RGB | 256 Farben |
-|---|---|---|
-| Code / Hilfelink | 5,72:1 | 5,45:1 |
-| Aktiver Titel | 6,38:1 | 6,10:1 |
-| Menü / Dialog / deaktivierter Text | 9,04:1 | 8,83:1 |
-| Status / Werkzeuge | 7,33:1 | 7,75:1 |
-| Auswahl | 21,00:1 | 21,00:1 |
-| Linkfokus / Debugger | 13,29:1 | 12,97:1 |
-| Breakpoint | 7,75:1 | 7,44:1 |
-| Ausführung / Handle | 19,69:1 | 19,72:1 |
+| Rolle | RGB-Kontrast |
+|---|---|
+| Code / Hilfelink | 5,72:1 |
+| Aktiver Titel | 6,38:1 |
+| Menü / Dialog / deaktivierter Text | 9,04:1 |
+| Status / Werkzeuge | 7,33:1 |
+| Auswahl | 21,00:1 |
+| Linkfokus / Debugger | 13,29:1 |
+| Breakpoint | 7,75:1 |
+| Ausführung / Handle | 19,69:1 |
### Prüffolge für Change 05
@@ -188,10 +187,10 @@ Linux amd64/xterm, Linux amd64/VTE, Linux arm64/xterm und Linux arm64/VTE:
Farbbeschriftungen und Handles; ForeColor/BackColor der Vorschau bleiben
unabhängig vom IDE-Theme.
6. Dieselben Ansichten mit absichtlich abweichender ANSI-Palette prüfen.
- RGB-/256-Farben der IDE müssen stabil bleiben. Transparenz und
+ RGB-Farben von IDE und BASIC-Ausgabe müssen stabil bleiben. Transparenz und
Inaktivitätsdimmung getrennt ein-/ausschalten und Abweichungen als
- Emulatorverhalten dokumentieren. Für 16-Farb-Fallback das tatsächlich
- geeignete Profil und verbleibende Unterschiede festhalten.
+ Emulatorverhalten dokumentieren. Auch ohne `COLORTERM`, mit `TERM=vt100`
+ und mit `NO_COLOR=1` muss die Anwendung dieselben RGB-Werte ausgeben.
7. Options ändern, speichern und neu starten; Auswahl muss erhalten sein.
IDE beenden: globale Palette und normale Shell-Anzeige müssen erhalten sein.
diff --git a/openspec/changes/phase-6-05-plattformmatrix/design.md b/openspec/changes/phase-6-05-plattformmatrix/design.md
index 22aa012..6e3fa4d 100644
--- a/openspec/changes/phase-6-05-plattformmatrix/design.md
+++ b/openspec/changes/phase-6-05-plattformmatrix/design.md
@@ -19,6 +19,8 @@ Phase 5 verwendete crossterm-Ereignisse, ratatui-TestBackend und `tests/support/
7. Den ergänzenden Theme-Change 05a vor Abschluss der visuellen Matrix berücksichtigen. Dessen Referenzansichten und Kontrastprüfungen mit tatsächlicher Revision, Farbprofil und Standard-/abweichender ANSI-Palette in jeder Pflichtzelle prüfen; vor einer Theme-Korrektur erhobene betroffene Bilder erneut aufnehmen. Diese Nachweise gehen mit der bestehenden Matrix an 06/07.
+8. Benutzerbefund vom 07.09.2026: ANSI-/256-Fallback entfernen und die vorhandene Farbabbildung in tb-ui für IDE und BASIC gemeinsam nutzen. RGB-Ausgabe auch bei fehlenden Farbfähigkeitsvariablen und NO_COLOR erzwingen. Fenster vor Block/Paragraph leeren, da diese nur Stile setzen und sonst Desktopmuster oder verdeckte Texte durchscheinen. Regressionen prüfen tatsächliche Leerzeichen, Auswahl/Neuzeichnen, absolute Farbwerte und ausgegebene Escape-Sequenzen. Frühere visuelle Nachweise gelten nicht für diesen korrigierten Stand.
+
## Risks / Trade-offs
- CI ohne interaktiven Desktop → automatisierte Zieltests plus zugeordnete manuelle Emulatornachweise, keine Gleichsetzung beider Kategorien.
diff --git a/openspec/changes/phase-6-05-plattformmatrix/proposal.md b/openspec/changes/phase-6-05-plattformmatrix/proposal.md
index 0e2efa2..ca383fa 100644
--- a/openspec/changes/phase-6-05-plattformmatrix/proposal.md
+++ b/openspec/changes/phase-6-05-plattformmatrix/proposal.md
@@ -18,7 +18,8 @@ Die bisherige headless Abnahme und der lokale Unix-PTY-Test beweisen nicht die e
### Modified Capabilities
-Keine. Bestehende IDE- und Host-Eingabeverträge bleiben maßgeblich.
+- `ide-oberflaeche`: Durchgehend absolute RGB-Farben und vollständig geleerte Fensterflächen; Benutzerkorrektur vom 07.09.2026.
+- `textbildschirm`: Dieselbe absolute DOS-RGB-Palette für programmbestimmte BASIC-Farben und Formularvorschau.
## Impact
diff --git a/openspec/changes/phase-6-05-plattformmatrix/tasks.md b/openspec/changes/phase-6-05-plattformmatrix/tasks.md
index da2b71e..5ab64d7 100644
--- a/openspec/changes/phase-6-05-plattformmatrix/tasks.md
+++ b/openspec/changes/phase-6-05-plattformmatrix/tasks.md
@@ -1,8 +1,8 @@
## 1. Prüfpfade vorbereiten
-- [ ] 1.1 Verbindliche Matrix und kurze Bediensequenzen für die vier Targets anlegen; Windows Terminal, Terminal.app sowie xterm/VTE auf beiden Linux-Architekturen müssen konkrete Versions-/Revisionsfelder und erwartete Resultate besitzen.
+- [x] 1.1 Verbindliche Matrix und kurze Bediensequenzen für die vier Targets anlegen; Windows Terminal, Terminal.app sowie xterm/VTE auf beiden Linux-Architekturen müssen konkrete Versions-/Revisionsfelder und erwartete Resultate besitzen.
- [ ] 1.2 Bestehenden Unix-PTY-Test und passenden Windows-Konsolen-/ConPTY-Test für Shell, Abbruch und Wiederherstellung bereitstellen; positive Läufe und erzwungene Fehler dürfen keinen beschädigten Terminalmodus hinterlassen.
-- [ ] 1.3 Die gemeinsame TBL-Verbraucherprobe aus 03 samt fester Prüfsumme in jede der vier Zielabnahmen aufnehmen (quellfreies TBC/EXE, RUN, tatsächliches Host-Target); IDE-/Programmprüfungen für F1F12, Modifikatoren, Maus, Unicode/Attribute und Resize nachvollziehbar auflisten; jeder Fall muss sichtbaren Effekt und Ausschluss doppelter BASIC-Zustellung prüfen.
+- [x] 1.3 Die gemeinsame TBL-Verbraucherprobe aus 03 samt fester Prüfsumme in jede der vier Zielabnahmen aufnehmen (quellfreies TBC/EXE, RUN, tatsächliches Host-Target); IDE-/Programmprüfungen für F1F12, Modifikatoren, Maus, Unicode/Attribute und Resize nachvollziehbar auflisten; jeder Fall muss sichtbaren Effekt und Ausschluss doppelter BASIC-Zustellung prüfen.
## 2. Tatsächliche Zielabnahme
@@ -16,3 +16,11 @@
- [ ] 3.1 Terminalgrenzen und verifizierte Ersatzwege dokumentieren; kein Produktfehler oder fehlender Prüfrechner darf als bestandene Matrixzelle erscheinen.
- [ ] 3.2 Automatisierbare Zielprüfungen an 06 übergeben und Matrixbericht fertigstellen; alle Pflichtzellen, Revisionen, vier Ergebnisse derselben TBL-Verbraucherprobe und tatsächlichen Ergebnisse müssen vor Abschluss vorliegen. Die weiterhin offenen Archivaufgaben 05a/1.2 (konkretes Benutzerprofil, frische/gespeicherte Optionen, Transparenz/Dimmung), 05a/3.2 (visuelle Matrix) und 05a/3.3 (befundfreie Schlussverifizierung) müssen hier tatsächlich abgeschlossen werden. Die visuellen Nachweise müssen das umgesetzte Theme aus 05a einschließlich Profil-/Farbmodus und relevanter UI-Zustände abdecken. Native Prüfsysteme dürfen separat vom zentralen Buildrunner betrieben und Nachweise dokumentiert manuell erhoben werden.
+
+Arbeitsstand 07.09.2026: Matrix/Bedienfolgen in `docs/plattformmatrix.md`, Eingabeprobe `tests/platform/input.frm`, gemeinsamer Aufruf `tests/support/platform-abnahme.py`. 1.2 ist implementiert und für Windows cross-geprüft, bleibt bis zum tatsächlichen Windows-Konsolenlauf offen. Automatisierte macOS-arm64-Nachweise liegen unter `evidence/2026-09-07-macos-arm64`; sie schließen keine manuelle Terminalzelle. Details und offene Befunde: `verification.md`.
+
+RGB-Korrektur nach Benutzerbefund vom 07.09.2026 gehört zu 2.5: keine
+ANSI-/256-Fallbackfarben; gemeinsame feste RGB-Palette für IDE und BASIC;
+Fensterflächen ohne durchscheinendes Desktopmuster. Neue Delta-Spezifikationen
+in `ide-oberflaeche` und `textbildschirm` ersetzen den früheren Fallbackvertrag.
+Die betroffene visuelle Zielprüfung bleibt bis zur echten Wiederholung offen.
diff --git a/tests/support/ide-execution-pty.py b/tests/support/ide-execution-pty.py
index b422070..ee369fa 100644
--- a/tests/support/ide-execution-pty.py
+++ b/tests/support/ide-execution-pty.py
@@ -2,6 +2,7 @@
"""Headless Unix PTY smoke: real terminal handoff, child Ctrl+C and IDE resume.
Run after cargo build -p tb-ide: python3 tests/support/ide-execution-pty.py
"""
+import argparse
import fcntl
import os
from pathlib import Path
@@ -13,10 +14,10 @@ import tempfile
import termios
import time
-binary = Path(__file__).resolve().parents[2] / 'target/debug/tb'
-def exercise(abort=False, file_shell=False):
+
+def exercise(binary, transcript_dir=None, abort=False, file_shell=False):
with tempfile.TemporaryDirectory(prefix='tb-pty-') as directory:
source = Path(directory) / 'main.bas'
command = "printf '\\nWAITING_CHILD\\n'; exec sleep 30" if abort else "printf '\\nSHELL_PROOF\\n'; exit 7"
@@ -32,7 +33,7 @@ def exercise(abort=False, file_shell=False):
os.dup2(slave, fd)
os.close(master)
os.close(slave)
- os.environ['TERM'] = 'xterm-256color'
+ os.environ.setdefault('TERM', 'xterm-256color')
os.environ['XDG_CONFIG_HOME'] = directory
# Keep the controlling terminal alive while checking restoration.
signal.signal(signal.SIGINT, lambda *_: None)
@@ -83,7 +84,17 @@ def exercise(abort=False, file_shell=False):
transcript.extend(os.read(master, 65536))
assert transcript.count(b'?1049h') >= 2
assert transcript.count(b'?1049l') >= 2
+ # Tatsächliche Ausgabe muss auch bei NO_COLOR/fehlender Erkennung
+ # dieselben absoluten IDE-Farben enthalten, ohne Palettenänderung.
+ assert b'38;2;170;170;170' in transcript
+ assert b'48;2;0;0;170' in transcript
+ assert b'48;2;170;0;170' in transcript
+ assert b'38;5;' not in transcript and b'48;5;' not in transcript
+ assert b'\x1b]' not in transcript
finally:
+ if transcript_dir:
+ name = 'file-shell' if file_shell else 'shell-abort' if abort else 'shell-exit'
+ (transcript_dir / (name + '.ansi')).write_bytes(transcript)
if not reaped:
os.killpg(pid, signal.SIGKILL)
os.waitpid(pid, 0)
@@ -91,6 +102,18 @@ def exercise(abort=False, file_shell=False):
os.close(slave)
-for kwargs in ({}, {'abort': True}, {'file_shell': True}):
- exercise(**kwargs)
- print('PASS', kwargs or {'shell_exit': 7})
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--binary', type=Path, default=Path(__file__).resolve().parents[2] / 'target/debug/tb')
+ parser.add_argument('--transcript-dir', type=Path)
+ args = parser.parse_args()
+ binary = args.binary.resolve(strict=True)
+ if args.transcript_dir:
+ args.transcript_dir.mkdir(parents=True, exist_ok=True)
+ for kwargs in ({}, {'abort': True}, {'file_shell': True}):
+ exercise(binary, args.transcript_dir, **kwargs)
+ print('PASS', kwargs or {'shell_exit': 7})
+
+
+if __name__ == '__main__':
+ main()

View File

@@ -0,0 +1,899 @@
Compiling tb-ui v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ui)
Compiling tb-vm v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-vm)
Compiling tb-export v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-export)
Compiling tb-runner v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-runner)
Compiling tb-ide v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-ide)
Compiling tb-cli v0.1.0 (/Users/gb/Projects/TerminalBasic/crates/tb-cli)
Finished `test` profile [unoptimized + debuginfo] target(s) in 8.84s
Running unittests src/main.rs (target/debug/deps/tbc-a3652d07eb423d84)
running 7 tests
test ide_acceptance::real_native_ide_exports_and_source_free_cli_library_consumer ... ignored, Echter Zielnachweis: TB_IDE_RUNTIME_DIR muss geprüfte Host-tbrt-Vorlage enthalten
test ide_acceptance::reference_matrix_is_complete_and_points_to_runnable_tests ... ok
test ide_acceptance::function_keys_conflict_contexts_and_errors_keep_a_single_input_owner ... ok
test ide_acceptance::partial_save_and_external_edit_recover_without_losing_documents ... ok
test ide_acceptance::saved_project_ide_source_cli_and_tbc_have_identical_sessions ... ok
test ide_acceptance::create_save_debug_help_exports_and_reopen ... ok
test ide_acceptance::every_menu_command_has_native_keyboard_and_mouse_routing ... ok
test result: ok. 6 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.98s
Running tests/compat.rs (target/debug/deps/compat-f89983ce5e40f582)
running 18 tests
test erzeuge_sollausgaben ... ignored, erzeugt Golden Files; nur bewusst aufrufen
test uhr_direktive_wird_gelesen ... ok
test zeilenanzahl_wird_benannt ... ok
test fehlende_und_unerwartete_attributebene_werden_benannt ... ok
test zeittrap_ohne_uhr_direktive_wird_abgewiesen - should panic ... ok
test textabweichung_nennt_zeile_soll_und_ist ... ok
test farbabweichung_nennt_zeile_spalte_soll_und_ist ... ok
test ereignisfolge_liefert_taste_maus_und_zeit_in_reihenfolge ... ok
test fehlende_groessenangabe_wird_abgewiesen ... ok
test release_formular_vergleicht_zeichen_und_attribute_unabhaengig ... ok
test prozessfrist::frist_beendet_blockierten_testprozess ... ok
test tbc_build_erzeugt_tbc ... ok
test tbc_run_hello ... ok
test tbc_run_stop_exitcode ... ok
test tbc_run_laufzeitfehler_exitcode ... ok
test tbc_loest_include_projekt_und_run_datei_auf ... ok
test tbc_isam_open_in_transaktion_hat_eine_harte_frist ... ok
test korpus_laeuft_mit_korrekter_ausgabe ... ok
test result: ok. 17 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.56s
Running tests/convert_frm.rs (target/debug/deps/convert_frm-cba5b52e462e1f23)
running 1 test
test conversion_failure_leaves_no_output ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/foreign.rs (target/debug/deps/foreign-92ee6191b667aed6)
running 3 tests
test oeffentliche_formularprogramme_sind_reproduzierbar_bedienbar ... ignored, TB_VBDOS_REPO muss auf cout/vbdos in der dokumentierten Revision zeigen
test fehlender_oder_falscher_fremdbestand_wird_benannt ... ok
test prozessfrist::frist_beendet_blockierten_testprozess ... ok
test result: ok. 2 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.11s
Running tests/inventar.rs (target/debug/deps/inventar-4656d211fa731f53)
running 17 tests
test signatur_greift_ohne_laufzeitverhalten ... ok
test bislang_signaturlose_elemente_haben_signaturen ... ok
test dokumentierte_elemente_werden_namentlich_abgewiesen ... ok
test offene_elemente_erzeugen_keinen_syntaxfehler ... ok
test statusvokabular_ist_beschraenkt ... ok
test offene_elemente_haben_keine_fundstelle ... ok
test abdeckungsstand_wird_ausgewiesen ... ok
test non_features_nennen_eine_fundstelle_in_der_sprachreferenz ... ok
test implementiert_mit_unsupported_marker_faellt_durch ... ok
test falscher_status_und_fehlende_runtimebindung_werden_namentlich_erkannt ... ok
test falsch_gesetzte_eigenschaft_wird_erkannt ... ok
test forms_inventar_stimmt_mit_der_klassentabelle_ueberein ... ok
test forms_absenkung_muss_den_richtigen_member_erreichen ... ok
test gemeinsam_dispatchte_konverter_brauchen_den_richtigen_typselektor ... ok
test inventar_stimmt_mit_code_ueberein ... ok
test jeder_inventareintrag_erreicht_hir_und_laufzeitziel ... ok
test falsche_gueltige_builtinbindung_und_entfernte_absenkung_fallen_durch ... ok
test result: ok. 17 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s
Running tests/project.rs (target/debug/deps/project-af9c39c0e4caf174)
running 8 tests
test designer_frm_is_accepted_by_the_standalone_cli_compiler ... ok
test native_build_optionen_sind_explizit_und_tbc_bleibt_standard ... ok
test release_projekt_prueft_anfangsdateien_dateieffekte_und_include_fehler ... ok
test explicit_startup_order_and_form_selection_survive_tbc ... ok
test cli_meldet_physische_quellorte_auch_nach_verschachtelten_includes ... ok
test library_cli_links_without_sources_and_protects_all_inputs_and_outputs ... ok
test projekt_und_formular_laufen_nach_entfernen_saemtlicher_quellen ... ok
test erlaubte_grafik_und_benannte_nonfeatures_werden_per_cli_geprueft ... ok
test result: ok. 8 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s
Running unittests src/lib.rs (target/debug/deps/tb_export-740bf84213e12075)
running 3 tests
test tests::fehlgeschlagene_native_finalisierung_erhaelt_das_ziel ... ok
test tests::publication_preserves_originals_on_conflict_failure_race_and_cancel ... ok
test tests::targets_versions_integrity_and_payload_bounds_are_enforced ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
Running unittests src/bin/tb-template.rs (target/debug/deps/tb_template-158083ae68069508)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/tb_frontend-668b07f10bc30f2a)
running 52 tests
test lexer::tests::strings_mit_doppelten_anfuehrungszeichen ... ok
test lexer::tests::zeilenfortsetzung ... ok
test lexer::tests::bang_ist_suffix_oder_containeroperator_nach_folgetoken ... ok
test lexer::tests::suffix_macht_keyword_zum_bezeichner ... ok
test lexer::tests::bezeichner_mit_punkt_und_unterstrich ... ok
test lexer::tests::keywords_case_insensitiv ... ok
test parser::tests::operator_prioritaet ... ok
test lexer::tests::kommentare_und_rem ... ok
test parser::tests::forms_syntax ... ok
test parser::tests::schleifen ... ok
test lexer::tests::literal_typisierung ... ok
test parser::tests::select_case ... ok
test parser::tests::zuweisung_und_aufruf ... ok
test parser::tests::print_varianten ... ok
test parser::tests::prozeduren ... ok
test parser::tests::if_block_und_einzeilig ... ok
test parser::tests::fehlerbehandlung ... ok
test parser::tests::typen_und_deklarationen ... ok
test sema::tests::hir_select_wird_abgesenkt ... ok
test sema::tests::hir_intdiv_rundet_operanden ... ok
test sema::tests::hir_konvertierung_materialisiert ... ok
test sema::tests::dreizeiler_wird_namentlich_statt_falsch_abgewiesen ... ok
test sema::tests::datei_ea_und_events ... ok
test sema::tests::deftype_regeln ... ok
test sema::tests::funktionsaufruf_und_rueckgabe ... ok
test sema::tests::hir_byref_und_byval ... ok
test sema::tests::def_fn_blockform ... ok
test sema::tests::argumentanzahl ... ok
test sema::tests::byref_verlangt_exakten_typ ... ok
test sema::tests::hardware_features_zur_compilezeit_abgelehnt ... ok
test sema::tests::arrays_implizit_und_explizit ... ok
test sema::tests::dialoge_listindex_und_zeichenmessung_werden_abgesenkt ... ok
test sema::tests::ereignisprozedur_signatur_und_normale_unterstrich_sub ... ok
test sema::tests::hir_slots_aufgeloest ... ok
test sema::tests::createindex_ohne_obergrenze_der_spaltenzahl ... ok
test sema::tests::isam_argumentanzahl_nennt_das_element ... ok
test sema::tests::isam_anweisung_parst_ohne_diagnose ... ok
test sema::tests::builtins_typen ... ok
test sema::tests::konstantenfaltung ... ok
test sema::tests::label_pruefung ... ok
test sema::tests::mid_anweisung ... ok
test sema::tests::option_explicit ... ok
test sema::tests::eigenschaftstyp_schreibbarkeit_bang_und_hir_index ... ok
test sema::tests::rollback_all_wird_als_sentinel_abgesenkt ... ok
test sema::tests::isam_satzargument_wird_typgeprueft ... ok
test sema::tests::typeof_form_control_und_form_metabefehl ... ok
test sema::tests::objektpfade_werden_namentlich_geprueft_und_udt_bleibt_unveraendert ... ok
test sema::tests::typkonflikt_wird_erkannt ... ok
test sema::tests::unbekanntes_unterprogramm ... ok
test sema::tests::shared_und_common ... ok
test sema::tests::kein_isam_element_ist_unbekannter_bezeichner ... ok
test sema::tests::udt_feldtypen ... ok
test result: ok. 52 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/corpus.rs (target/debug/deps/corpus-0371f2991a3b061a)
running 1 test
test korpus_parst_und_wird_typgeprueft ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/editing.rs (target/debug/deps/editing-01467d91310d70f2)
running 4 tests
test incomplete_is_parser_context_not_a_second_grammar ... ok
test normalization_protects_tokens_and_is_idempotent ... ok
test deftype_context_does_not_leak_from_another_procedure ... ok
test declaration_maintenance_and_procedure_generation_are_transactional ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/tb_ide-43621e6718da8b87)
running 3 tests
test export::tests::delayed_result_is_published_only_after_ui_validation_and_success_stays_success ... ok
test export::tests::replaced_library_invalidates_pending_export_even_with_same_length ... ok
test export::tests::cancelled_superseded_changed_and_foreign_jobs_never_publish ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
Running unittests src/main.rs (target/debug/deps/tb-f285a33b6fdb6251)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/app.rs (target/debug/deps/app-d3d6e33413d63d0f)
running 20 tests
test theme_color_commands_use_explicit_values_and_never_modify_terminal_palette ... ok
test executable_target_validation_and_missing_template_are_honest ... ok
test start_snapshot_and_reference_menus_have_all_commands ... ok
test dispatcher_keeps_function_keys_and_copy_out_of_basic_input ... ok
test designer_form_list_and_context_availability_match_the_active_window ... ok
test windows_restore_geometry_and_resize_without_document_loss ... ok
test theme_windows_erase_desktop_and_underlying_windows_on_every_redraw ... ok
test modal_focus_and_dirty_project_cancel_preserve_document ... ok
test options_apply_persist_across_bases_and_preserve_unsaved_values_on_error ... ok
test clicking_project_members_and_dialog_choices_transfers_focus_and_applies_values ... ok
test theme_designer_chrome_and_handles_preserve_program_palette ... ok
test status_and_project_buttons_use_the_keyboard_command_path ... ok
test mouse_focus_is_modal_and_program_abort_is_not_editor_copy ... ok
test debugger_reports_absent_library_source_and_can_step_back_to_caller ... ok
test file_picker_save_conflict_and_project_replacement_use_real_events ... ok
test finalization_failure_is_reported_and_preserves_existing_output ... ok
test exit_with_partial_save_retains_dialog_and_unsaved_documents ... ok
test real_library_export_uses_dirty_include_and_mouse_without_runtime_template ... ok
test export_dialogs_validate_and_render_all_results_without_creating_files ... ok
test theme_contrast_covers_rendered_states_and_rejects_bad_pairs ... ok
test result: ok. 20 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.41s
Running tests/debugger.rs (target/debug/deps/debugger-18aeee8ea147e9f4)
running 6 tests
test form_handler_keys_and_old_revision_cursor_guard ... ok
test immediate_wait_run_transition_watchpoint_and_trace_are_session_owned ... ok
test includes_and_deleted_marks_do_not_bind_to_foreign_statements ... ok
test dispatcher_marks_edits_rebind_and_readonly_watches ... ok
test calls_procedure_step_cursor_history_and_restart ... ok
test calls_mouse_inspection_instant_key_delete_and_error_window ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
Running tests/designer.rs (target/debug/deps/designer-1f0dd9e883e87225)
running 18 tests
test menu_indent_outdent_rename_and_field_changes_have_one_undo ... ok
test property_bar_types_readonly_and_no_status_line ... ok
test imported_type_shadow_is_not_an_object_reference_during_rename ... ok
test clipboard_children_arrays_and_delete_preserve_code ... ok
test binary_designer_import_requires_text_target_and_compiles ... ok
test event_array_signature_twice_and_shared_form_identity ... ok
test bound_rename_across_documents_is_atomic_and_undo_restores_all ... ok
test container_multiselect_keyboard_drag_resize_and_undo ... ok
test toolbox_double_click_tool_drag_grid_and_window_form_selection ... ok
test menu_hierarchy_arrays_shortcuts_and_palette_use_events ... ok
test rename_preserves_shadowed_names_and_handles_form_aliases_and_include_calls ... ok
test indexed_controls_merge_atomically_with_bindings_and_undo ... ok
test events_with_the_same_name_are_local_to_their_form ... ok
test shared_include_ambiguity_aborts_rename_and_event_defaults_use_includes ... ok
test property_menu_buttons_palette_drag_and_modal_mouse_are_connected ... ok
test all_metadata_tools_persist_and_preview_matches_runtime_without_events ... ok
test all_eight_handles_resize_transactionally_and_preserve_identity_after_structure_edits ... ok
test existing_default_typed_event_and_all_metadata_signatures_compile ... ok
test result: ok. 18 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
Running tests/documents.rs (target/debug/deps/documents-6a65482da983e004)
running 15 tests
test unrepresentable_save_paths_fail_before_writing_or_changing_documents ... ok
test save_plan_prevents_same_destination_and_project_overwrite ... ok
test shared_views_text_import_and_undo_are_document_transactions ... ok
test failed_open_add_and_cancel_do_not_replace_existing_documents ... ok
test aliases_share_documents_and_form_code_offsets_match_saved_text ... ok
test binary_import_only_saves_to_explicit_text_target_and_preserves_original_forever ... ok
test edited_include_wins_in_both_modules_without_writing_the_expansion ... ok
test startup_removal_requires_explicit_replacement_and_never_deletes_a_file ... ok
test libraries_are_binary_members_with_relative_save_as_and_no_source_views ... ok
test project_save_as_rebases_members_and_keeps_include_targets ... ok
test external_changes_and_save_as_collisions_need_explicit_decisions ... ok
test module_save_as_rebases_includes_and_failed_save_does_not_change_identity_or_text ... ok
test cursor_after_shortening_uses_original_position_and_open_after_save_uses_fresh_disk ... ok
test partial_save_preserves_unsaved_data_and_does_not_finish_close ... ok
test project_create_save_reopen_preserves_forms_members_startup_and_cli_sources ... ok
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
Running tests/editor.rs (target/debug/deps/editor-b6dc665e95525030)
running 7 tests
test search_scope_direction_wrap_and_shortening_replace_all ... ok
test crlf_navigation_word_boundaries_readonly_and_normalization_keep_views ... ok
test cua_unicode_overwrite_wordstar_and_shared_undo ... ok
test line_leave_incomplete_invalid_disabled_and_current_revisions ... ok
test includes_and_diagnostic_selection_use_original_physical_files ... ok
test procedures_and_shared_save_hook_preserve_drafts_and_frm_code ... ok
test external_include_revision_and_project_context_declarations ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
Running tests/execution.rs (target/debug/deps/execution-bf913fdae7aacbc3)
running 13 tests
test shell_request_is_resumed_once_and_error_is_basic_error ... ok
test named_run_of_current_target_uses_edited_sources ... ok
test comment_only_source_change_also_requires_revision_choice ... ok
test ide_and_cli_run_wrapper_match_input_time_and_forms_script ... ok
test f5_input_pause_copy_and_single_delivery_keep_the_same_vm ... ok
test output_viewports_resize_and_end_preserve_buffer_and_basic_colors ... ok
test print_uses_utf8_protected_output_and_does_not_mutate_source ... ok
test changed_paused_source_requires_explicit_old_revision_or_restart ... ok
test explicit_end_and_system_end_visible_forms_but_natural_end_keeps_them_live ... ok
test restart_command_run_targets_and_missing_target_preserve_documents ... ok
test restart_drops_files_data_traps_and_queued_actions ... ok
test startup_selection_matches_shared_cli_pipeline_and_legacy_mak ... ok
test platform_probe_records_each_basic_key_once_and_keeps_ide_keys_out ... ok
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
Running tests/help.rs (target/debug/deps/help-ff16d5fb3c397804)
running 11 tests
test offline_help_child ... ok
test markdown_blocks_escapes_links_and_unicode_layout ... ok
running 1 test
test catalog_links_and_missing_targets_are_checked ... ok
test right_click_configuration_and_external_link_follow ... ok
test links_history_resize_scroll_and_focus_use_real_events ... ok
test every_command_and_forms_alias_resolves_and_invalid_alias_fails ... ok
test twenty_back_steps_end_of_contents_and_all_entries ... ok
test offline_help_child ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 10 filtered out; finished in 0.07s
test help_works_from_a_directory_without_checkout ... ok
test help_preserves_fullscreen_and_does_not_modify_designer_or_source ... ok
test context_functions_properties_commands_and_unknown_queries ... ok
test code_and_tables_remain_reachable_after_resize_and_horizontal_scroll ... ok
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.14s
Running unittests src/lib.rs (target/debug/deps/tb_runner-d3339a5a0df9c7dd)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/bin/tbrt.rs (target/debug/deps/tbrt-a6c3f62502f5f188)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/lib.rs (target/debug/deps/tb_runtime-6b2bc969f5367a62)
running 140 tests
test builtins::tests::benutzertaste_ueber_key_anweisung ... ok
test builtins::tests::nicht_getrappte_taste_bleibt_im_strom ... ok
test builtins::tests::getrappte_taste_verlaesst_den_eingabestrom ... ok
test builtins::tests::off_wirkt_auf_bereits_wartende_tasten ... ok
test builtins::tests::input_s_dispatch_bewahrt_codepointreste_auch_ohne_vm ... ok
test builtins::tests::signal_aus_dem_host_meldet_den_trap ... ok
test builtins::tests::zonenquelle_ausfall_und_erholung_gelten_fuer_alle_zeitfunktionen ... ok
test builtins::tests::benutzertaste_ohne_umschalt_bleibt_im_strom ... ok
test builtins::tests::builtin_ueber_tabelle ... ok
test builtins::tests::signal_stop_merkt_beide_quellen_und_off_verwirft ... ok
test console::tests::volle_zone_springt_zur_uebernaechsten ... ok
test console::tests::druckzone_folgt_dem_cursor_nach_locate ... ok
test builtins::tests::key_stop_merkt_einmal_und_laesst_alle_tasten_auch_nach_on_lesbar ... ok
test builtins::tests::mausereignisse_haeufen_sich_nicht_an ... ok
test builtins::tests::sigint_ohne_aktiven_trap_bleibt_abbruch ... ok
test console::tests::druckzonen ... ok
test builtins::tests::setuevent_meldet_das_benutzerereignis ... ok
test builtins::tests::stringfunktionen_randfaelle ... ok
test console::tests::zahlen_mit_vorzeichenspalte ... ok
test datetime::tests::fester_versatz_verschiebt_genau ... ok
test datetime::tests::gueltigkeitspruefung ... ok
test datetime::tests::programmversatz_kommt_hinzu ... ok
test datetime::tests::serienwert_und_rueckumwandlung ... ok
test datetime::tests::tageszeit ... ok
test datetime::tests::text_in_serienwert ... ok
test datetime::tests::wochentag_sonntag_ist_eins ... ok
test datetime::tests::zaehlweise_des_vorbilds ... ok
test errors::tests::bekannte_codes ... ok
test errors::tests::forms_und_isam_codes ... ok
test errors::tests::unbelegte_codes_sind_unprintable ... ok
test fileio::tests::felder_einer_input_zeile ... ok
test fileio::tests::modus_aus_kurzsyntax ... ok
test fileio::tests::muster_mit_stern_und_fragezeichen ... ok
test finanz::tests::abschreibungen ... ok
test finanz::tests::barwert_und_endwert_sind_gegenlaeufig ... ok
test fileio::tests::festes_feld_ist_inhaltsunabhaengig_gleich_lang ... ok
test fileio::tests::rset_schneidet_ab_und_richtet_rechts_aus ... ok
test finanz::tests::endwert_einer_rentenzahlung ... ok
test finanz::tests::irr_ohne_vorzeichenwechsel_ist_fehler ... ok
test finanz::tests::kapitalwert_und_interner_zinsfuss ... ok
test finanz::tests::mirr_liegt_zwischen_den_zinsen ... ok
test finanz::tests::nper_zaehlt_die_perioden ... ok
test finanz::tests::ungueltige_argumente ... ok
test finanz::tests::zins_und_tilgung_ergeben_die_zahlung ... ok
test finanz::tests::zinssatz_wird_zurueckgerechnet ... ok
test format::tests::currency_darstellung ... ok
test format::tests::double_darstellung ... ok
test format::tests::ganzzahlen ... ok
test format::tests::single_darstellung ... ok
test format::tests::val_parsen ... ok
test host::tests::capture_host_haelt_zeichen_und_attribut ... ok
test host::tests::maus_und_tasten_behalten_ihre_reihenfolge ... ok
test host::tests::virtuelle_uhr_laeuft_nur_wenn_gestellt ... ok
test host::tests::vorbereitete_zeile_wird_zu_tasten ... ok
test isam::tests::absteigende_spalte_kehrt_die_ordnung_um ... ok
test host::tests::groessenaenderung_und_abbruch_als_ereignis ... ok
test host::tests::verzoegertes_ereignis_kommt_nach_der_anzeige ... ok
test isam::tests::nullbyte_im_text_stoert_den_abschluss_nicht ... ok
test isam::tests::praefix_ende_zaehlt_hoch ... ok
test isam::tests::beschaedigte_datei_meldet_fehler_88 ... ok
test datetime::tests::versatz_liegt_im_gueltigen_bereich ... ok
test datetime::tests::versatz_gilt_je_zeitpunkt ... ok
test isam::tests::kodierung_erhaelt_die_ordnung_bei_zufallspaaren ... ok
test isam::tests::prozessfrist::frist_beendet_blockierten_testprozess ... ok
test isam::tests::close_beendet_keine_transaktion ... ok
test isam::tests::jede_satzoperation_erzeugt_einen_protokolleintrag ... ok
test isam::tests::textabschluss_ordnet_praefix_vor_laengerem_text ... ok
test isam::tests::neue_tabellen_und_aktive_indizes_verschwinden_bei_ruecknahme ... ok
test isam::tests::ungueltige_namen_werden_abgewiesen ... ok
test isam::tests::ohne_transaktion_kein_protokoll ... ok
test screen::tests::blink_wird_als_hell_simuliert ... ok
test isam::tests::saetze_nach_close_aller_bindungen_zuruecknehmen ... ok
test screen::tests::breites_zeichen_am_rechten_rand_bricht_ganz_um ... ok
test isam::tests::pfadaliase_teilen_transaktion_und_ruecknahme ... ok
test isam::tests::cursor_ueberlebt_eine_satzaenderung ... ok
test screen::tests::breites_zeichen_belegt_zwei_zellen ... ok
test isam::tests::geloeschte_satz_id_wird_nicht_neu_vergeben ... ok
test isam::tests::deletetable_loest_alle_betroffenen_bindungen ... ok
test isam::tests::gescheitertes_createindex_hinterlaesst_keinen_index ... ok
test screen::tests::dynamische_groesse_und_resize ... ok
test isam::tests::satz_roundtrip_ueber_alle_feldtypen ... ok
test screen::tests::locate_auf_fortsetzung_trifft_den_zeichenanfang ... ok
test isam::tests::delete_pufferfehler_erhaelt_saetze_indizes_und_cursor ... ok
test isam::tests::nicht_festgeschriebene_transaktion_verfaellt ... ok
test screen::tests::locate_prueft_grenzen ... ok
test screen::tests::print_schreibt_und_bewegt_cursor ... ok
test isam::tests::indexeintraege_stehen_in_schluesselreihenfolge ... ok
test isam::tests::struktur_undo_prueft_puffer_vor_dem_loeschen ... ok
test screen::tests::resize_folgt_vollem_scrollbereich ... ok
test isam::tests::hoehere_formatversion_wird_abgewiesen ... ok
test screen::tests::rueckschritt_loescht_ein_breites_zeichen_ganz ... ok
test screen::tests::resize_klemmt_nur_den_unteren_rand_wenn_der_bereich_bleibt ... ok
test screen::tests::resize_klemmt_cursor_und_scrollbereich ... ok
test screen::tests::scrollen_am_unteren_rand ... ok
test screen::tests::ueberschreiben_einer_haelfte_leert_die_andere ... ok
test screen::tests::ui_zeile_bleibt_am_rand_ohne_umbruch_oder_scrollen ... ok
test screen::tests::umbruch_am_rechten_rand ... ok
test screen::tests::unicode_zeichen_belegen_eine_zelle ... ok
test screen::tests::view_print_begrenzt_scrollen ... ok
test snapshot::tests::breites_zeichen_erscheint_einmal ... ok
test snapshot::tests::getrimmt_auf_die_belegte_flaeche ... ok
test snapshot::tests::leerer_bildschirm_ergibt_leeren_snapshot ... ok
test snapshot::tests::locate_erweitert_die_flaeche ... ok
test snapshot::tests::mit_farben_kommt_die_attributebene ... ok
test snapshot::tests::nachgestellte_leerzeichen_bleiben_erhalten ... ok
test snapshot::tests::ohne_farben_keine_attributebene ... ok
test traps::tests::aus_verwirft_auch_das_gemerkte ... ok
test traps::tests::aus_verwirft_kuenftige ... ok
test traps::tests::beide_umschaltbits_gelten_gleich ... ok
test traps::tests::benutzertaste_braucht_umschaltzustand_und_scancode ... ok
test traps::tests::event_off_verwirft_ereignisse_und_laesst_die_zustaende ... ok
test traps::tests::genauer_trap_geht_dem_sammeltrap_vor ... ok
test traps::tests::gestoppt_merkt_genau_eines ... ok
test traps::tests::gosub_null_schaltet_den_trap_ab ... ok
test traps::tests::kein_wiedereintritt_waehrend_der_handler_laeuft ... ok
test traps::tests::key_null_faengt_alle_tasten_der_tabelle ... ok
test traps::tests::off_im_handler_ueberlebt_das_return ... ok
test traps::tests::ohne_trap_kostet_nichts ... ok
test traps::tests::reihenfolge_key_vor_timer_vor_uevent_vor_signal ... ok
test traps::tests::return_schaltet_sonst_selbsttaetig_ein ... ok
test traps::tests::uebersprungene_fristen_zaehlen_als_eine ... ok
test traps::tests::vordefinierte_taste_wird_ueber_den_scancode_erkannt ... ok
test traps::tests::zeit_wird_erst_nach_der_frist_faellig ... ok
test using::tests::exponentialform ... ok
test using::tests::feldueberlauf_bekommt_prozentzeichen ... ok
test using::tests::format_wird_wiederverwendet ... ok
test using::tests::literale_und_unterstrich ... ok
test using::tests::numerisches_feld_mit_nachkommastellen ... ok
test using::tests::ohne_feld_aber_mit_werten_ist_fehler_5 ... ok
test using::tests::ohne_feld_und_ohne_werte_bleibt_der_text ... ok
test using::tests::stringfelder ... ok
test using::tests::tausendertrennung ... ok
test using::tests::vorzeichenfuehrung ... ok
test using::tests::waehrung_und_sterne ... ok
test value::tests::array_indexpruefung ... ok
test value::tests::banker_rounding ... ok
test value::tests::currency_rundung ... ok
test value::tests::overflow_bei_konvertierung ... ok
test isam::tests::savepoints_nehmen_index_und_tabellenstrukturen_zurueck ... ok
test isam::tests::symlink_neuanlage_behaelt_die_dateiidentitaet ... ok
test result: ok. 140 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.56s
Running unittests src/lib.rs (target/debug/deps/tb_ui-95ce53373c09fcd8)
running 66 tests
test forms::tests::hide_entlaedt_nicht_und_unload_ist_abbrechbar ... ok
test forms::tests::optionen_gruppieren_nach_unmittelbarem_container ... ok
test forms::tests::screen_folgt_resize ... ok
test forms::tests::menuregeln_weisen_separator_und_titel_shortcuts_ab ... ok
test forms::tests::combobox_dropdown_closes_on_focus_change_and_hide ... ok
test forms::tests::gleichzeitig_faellige_timer_laufen_nach_name ... ok
test forms::tests::fokus_tab_access_default_und_eingabeprioritaet ... ok
test forms::tests::screen_meldet_oberstes_formular_und_aktives_control ... ok
test forms::tests::modalfehler_400_bis_403 ... ok
test forms::tests::menues_zeichnen_und_navigieren_sechs_ebenen ... ok
test forms::tests::defaults_bereich_und_implizites_laden ... ok
test forms::tests::arrays_und_designzeitfehler ... ok
test forms::tests::freier_formularklick_und_doppelklick_werden_zugestellt ... ok
test forms::tests::simple_combo_zeichnet_listeninhalt_auswahl_und_begrenzung ... ok
test forms::tests::indexed_containers_preserve_parent_geometry_and_option_groups ... ok
test forms::tests::spin_zaehlt_zyklisch_und_wiederholt_nach_interval ... ok
test frm::tests::binary_layout_preserves_arrays_and_container_hierarchy ... ok
test forms::tests::timerarrays_holen_nur_aktive_zeit_nach_und_sortieren_nach_name_und_index ... ok
test frm::tests::binary_class_specific_properties_match_the_reference_exports ... ok
test forms::tests::zeichenbilder_aller_klassen_und_buttonhoehen ... ok
test forms::tests::timer_neustart_verwirft_alte_fristen_und_ereignisse ... ok
test frm::tests::binary_strings_are_decoded_from_cp437 ... ok
test forms::tests::listenauswahl_bleibt_beim_einfuegen_und_entfernen_konsistent ... ok
test frm::tests::binary_reader_rejects_magic_and_unknown_structure_with_offset ... ok
test frm::tests::binary_reader_rejects_out_of_range_property_with_offset ... ok
test frm::tests::reads_nested_form_and_preserves_source_exactly ... ok
test frm::tests::binary_strings_and_header_flags_map_to_the_named_properties ... ok
test frm::tests::spin_symbol_wird_als_bedienbares_control_erkannt ... ok
test frm::tests::text_form_control_arrays_share_one_catalog_object_and_keep_indices ... ok
test frm::tests::unsupported_binary_property_is_named_and_unclaimed_strings_still_fail ... ok
test frm::tests::writes_read_only_boolean_only_when_it_differs_from_false ... ok
test frm::tests::writes_no_properties_for_default_only_control ... ok
test frm::tests::writes_documented_d1_and_roundtrips_description ... ok
test frm::tests::rejects_version_class_property_value_and_open_block_with_location ... ok
test frm::tests::explicit_default_is_preserved_until_canonical_write ... ok
test frm_pcode::tests::decodes_defint_and_vbdos_isam_tokens ... ok
test frm_pcode::tests::decodes_vbdos_declarations_without_losing_type_or_bounds ... ok
test frm_pcode::tests::embedded_include_is_a_comment_but_literals_remain_unchanged ... ok
test frm::tests::canonical_index_zero_remains_an_array_and_array_parents_are_preserved ... ok
test frm::tests::unsupported_custom_control_is_named_and_not_emitted_as_screen ... ok
test frm_pcode::tests::next_trennt_eine_vorangehende_anweisung_auch_ohne_newline_token ... ok
test host::tests::ausserhalb_der_flaeche_wird_verworfen ... ok
test frm_pcode::tests::resets_block_local_default_types_before_the_next_block ... ok
test host::tests::backtab_ist_tab_mit_shift_auch_ohne_modifier ... ok
test frm::tests::reads_real_vbdos_form_with_complete_documented_description ... ok
test host::tests::letzte_zelle_liegt_noch_drin ... ok
test host::tests::maus_zaehlt_ab_eins ... ok
test host::tests::nach_groessenaenderung_gilt_die_neue_flaeche ... ok
test forms::tests::forms_dialoge_liefern_gruppen_eingabe_und_feste_groesse ... ok
test host::tests::rollrad_hat_keine_entsprechung ... ok
test host::tests::sondertaste_traegt_fuehrendes_nullzeichen ... ok
test host::tests::strg_c_ist_abbruch ... ok
test forms::tests::tabfolge_ueberspringt_und_laeuft_rueckwaerts ... ok
test host::tests::umschaltzustand_als_bitfeld ... ok
test host::tests::umschaltzustand_der_taste_wird_mitgefuehrt ... ok
test host::tests::zeichentaste_wird_zu_inkey_form ... ok
test screen::tests::farbabbildung ... ok
test signale::tests::marke_wird_zu_kennung_und_verfaellt_danach ... ok
test signale::tests::sigint_hat_vorrang_vor_sigterm ... ok
test signale::tests::echtes_signal_setzt_die_marke ... ok
test forms::tests::z_reihenfolge_dragdrop_und_timerreihenfolge ... ok
test forms::tests::text_liste_option_scroll_picture_und_dateisystem ... ok
test forms::tests::sorted_wechsel_erhaelt_auch_die_auswahl_zwischen_duplikaten ... ok
test forms::tests::zustandsfarben_laufen_fuer_alle_controls_ueber_denselben_zeichenpfad ... ok
test terminal::tests::terminal_cleanup_on_normal_error_and_partial_initialization ... ok
test forms::tests::listenrenderer_haelt_zellgrenzen_auch_an_bildschirmraendern_ein ... ok
test result: ok. 66 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.61s
Running unittests src/lib.rs (target/debug/deps/tb_vm-2a4538183acbb98c)
running 16 tests
test codegen::tests::fehlerbehandlung_emit ... ok
test codegen::tests::string_konkatenation ... ok
test codegen::tests::vergleich_und_logik ... ok
test codegen::tests::arrays_dim_und_zugriff ... ok
test bytecode::tests::instr_roundtrip ... ok
test codegen::tests::data_read_restore ... ok
test codegen::tests::tbc_roundtrip_ueber_codegen ... ok
test bytecode::tests::unbekannte_version_wird_abgelehnt ... ok
test bytecode::tests::tbc_roundtrip ... ok
test codegen::tests::ausdruck_monomorph_mit_conv ... ok
test codegen::tests::gosub_und_on_goto ... ok
test codegen::tests::for_mit_dynamischem_step ... ok
test codegen::tests::if_mit_fixup ... ok
test codegen::tests::prozedur_und_byref ... ok
test codegen::tests::for_mit_konstantem_step ... ok
test codegen::tests::function_liefert_wert ... ok
test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/cooperative.rs (target/debug/deps/cooperative-acdd5e7adb1bd680)
running 7 tests
test sleep_deadline_survives_pause_and_does_not_pop_argument_twice ... ok
test finite_slices_preserve_events_without_clock_queries ... ok
test common_dialog_external_call_keeps_byref_arguments_until_completion ... ok
test partial_console_operations_keep_prompt_arguments_and_assignment ... ok
test pending_dialog_keeps_physical_resize_and_modeless_handler_finishes_after_hide ... ok
test dialogs_keep_partial_text_focus_and_caller_arguments ... ok
test modal_and_modeless_forms_resume_handlers_across_tiny_budgets ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/debugger.rs (target/debug/deps/debugger-2aaa51d59ed630d5)
running 14 tests
test same_line_calls_and_immediate_do_not_retrigger_a_line_breakpoint ... ok
test set_next_rejects_loop_gosub_handler_and_pending_operations_atomically ... ok
test break_continue_loop_and_statement_steps ... ok
test form_event_breakpoint_and_step_over_keep_the_event_frame ... ok
test step_over_recursive_frames_history_and_safe_jump ... ok
test immediate_handler_exit_and_cached_new_imports_are_safe ... ok
test set_next_rejects_else_and_case_entry_but_allows_peer_statements ... ok
test immediate_handler_resume_and_break_on_errors ... ok
test gosub_step_over_breakpoint_priority_and_temporary_target_cleanup ... ok
test watches_immediate_byref_and_errors_preserve_continuation ... ok
test include_identity_qualified_globals_udts_and_older_frames ... ok
test immediate_wait_interrupt_nested_error_end_and_run ... ok
test watches_never_initialize_arrays_or_run_user_functions_and_keep_selected_context ... ok
test watchpoint_history_ring_and_error_differential ... ok
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/events.rs (target/debug/deps/events-2f0766815a3e98d6)
running 33 tests
test lokaler_error_handler_nach_trap_verwirft_abgebrochene_teileingabe ... ok
test handler_eingabe_und_stop_bewahren_die_aeussere_eingabe ... ok
test fehlender_ereignispfad_wird_namentlich_erkannt ... ok
test randomize_eingabe_teilt_den_fortsetzbaren_konsolenpfad ... ok
test input_redo_verliert_weder_referenzen_noch_teiltext_beim_trap ... ok
test fokus_handler_laufen_geordnet_bis_zur_rueckkehr ... ok
test ohne_zeitbedarf_keine_uhrabfrage_und_keine_mausansammlung ... ok
test offenes_menu_sperrt_traps_und_wartende_forms_timer_an_allen_zustellpunkten ... ok
test doevents_verhindert_rekursion_derselben_forms_prozedur ... ok
test formular_tastatur_fallback_fuehrt_alle_drei_basic_handler_aus ... ok
test intervallaenderung_im_handler_verwirft_bereits_faellige_altintervalle ... ok
test dialog_erhaelt_stop_markierung_unverbrauchter_tasten ... ok
test eof_im_dialog_eines_formularhandlers_beendet_die_ereignispumpe ... ok
test beide_signale_werden_zugestellt_und_beginnen_in_quellreihenfolge ... ok
test eof_leert_angenommene_eingaben_bei_modalen_und_modellosen_forms ... ok
test review_gleichzeitig_key_und_uevent_behalten_prioritaet ... ok
test return_label_verwirft_abgebrochene_eingabe_und_ausdruck ... ok
test review_menu_schliessen_stellt_pending_trap_vor_erneutem_warten_zu ... ok
test input_fortsetzung_erhaelt_teiltext_ausdruck_und_byref_ziel ... ok
test timer_waehrend_input_wird_vor_erneutem_warten_zugestellt ... ok
test review_maus_vor_getrappter_taste ... ok
test review_modale_eingabe_vor_eof_wird_zugestellt ... ok
test review_signal_waehrend_blockierender_eingabe ... ok
test unload_aus_unload_handler_ruft_dieselbe_prozedur_nicht_rekursiv_auf ... ok
test spaete_timeraktivierung_nutzt_hostzeit_auch_ueber_control_parameter ... ok
test key_on_off_stop_gelten_fuer_alle_konsoleneingaben ... ok
test taste_maus_und_trap_behalten_die_annahmereihenfolge ... ok
test review_objektargument_darf_gotfocus_nicht_vorziehen ... ok
test shell_und_irr_funktionssignaturen_erreichen_die_runtime ... ok
test registrierte_form_ereignisse_erreichen_basic_aus_realen_quellen ... ok
test sleep_folgt_nur_der_hostfrist_und_forms_timer_beendet_es ... ok
test prozessfrist::frist_beendet_blockierten_testprozess ... ok
test registrierte_control_ereignisse_erreichen_basic_aus_realen_quellen ... ok
test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
Running tests/incremental.rs (target/debug/deps/incremental-ef01ad977c2204da)
running 4 tests
test unused_constants_do_not_invalidate_independent_units_and_declare_changes_do ... ok
test includes_source_ids_data_and_forms_relink_without_patching_old_products ... ok
test cached_private_edits_parse_and_compile_only_changed_modules ... ok
test constants_types_common_removed_symbols_and_order_match_full_compiler ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/library.rs (target/debug/deps/library-0b9e1b471b1e7297)
running 9 tests
test source_debug_context_keeps_module_ids_when_library_is_between_sources ... ok
test library_include_locations_keep_physical_lines_and_basic_erl ... ok
test library_source_locations_and_run_restart_are_preserved ... ok
test unused_declare_is_not_an_import_but_references_in_any_body_must_resolve ... ok
test contracts_reject_result_common_bounds_and_invalid_declarations ... ok
test source_and_library_have_identical_types_constants_common_data_byref_and_init_order ... ok
test two_source_free_libraries_with_typed_open_imports_and_diagnostics ... ok
test library_container_rejects_truncation_versions_lengths_and_references ... ok
test separate_form_libraries_remap_nested_objects_initials_arrays_and_events ... ok
test result: ok. 9 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/project.rs (target/debug/deps/project-7ae350d75d21dcb2)
running 7 tests
test diagnose_im_include_und_echte_duplikate_bleiben_sichtbar ... ok
test typreferenzen_signaturen_und_option_base_ueber_modulgrenzen ... ok
test module_includes_erl_breakpoints_und_inspektion_behalten_ihren_ursprung ... ok
test lokale_prozeduren_shared_common_und_wiederholte_includes_bleiben_getrennt ... ok
test initialisierungen_def_fn_und_modulhandler_haben_eigene_quellorte ... ok
test beschaedigte_container_werden_vor_der_ausfuehrung_abgewiesen ... ok
test zwei_formulare_mit_gleichen_controls_und_arrays_laufen_aus_dem_kompilat ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/project_io.rs (target/debug/deps/project_io-76f82da2b295b346)
running 7 tests
test case_insensitive_parent_lookup_works_from_relative_base_without_changing_cwd ... ok
test overlays_keep_original_files_and_distinct_module_contexts ... ok
test documented_mak_example_is_accepted_by_the_shared_loader ... ok
test relative_include_precedes_search_paths_and_virtual_files_are_resolved ... ok
test cycle_missing_include_and_literal_directives_are_distinguished ... ok
test startup_metadata_roundtrip_and_invalid_contracts ... ok
test tbl_members_move_save_as_order_startup_and_same_size_cache_replacement ... ok
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
Running tests/project_regressions.rs (target/debug/deps/project_regressions-88b074fd28a1a09a)
running 16 tests
test diagnostik_bei_literalen_im_include_hat_die_exakte_spalte ... ok
test spaetere_common_konflikte_und_doppelte_module_werden_am_ursprung_gemeldet ... ok
test v6_linkerdiagnose_nennt_den_urspruenglichen_dateiort ... ok
test debugger_unterscheidet_typisierte_scalar_und_arraynamen ... ok
test prozedurlokaler_deftype_veraendert_keine_nachfolgende_signatur ... ok
test v5_load_und_unload_loesen_den_expliziten_container_auf ... ok
test v3_form_metabefehle_erzeugen_getrennte_formularobjekte ... ok
test common_vertraege_und_linkfehler_haben_echte_quellorte ... ok
test v4_common_behaelt_typidentitaet_und_initialisiert_arrays_einmal ... ok
test gemischte_frm_und_form_module_haben_getrennte_ereignisbindungen ... ok
test modulbreakpoint_rundlauf_inspektion_und_numerische_erl_bleiben_getrennt ... ok
test v1_zweige_und_schleifen_behalten_eigene_quellorte ... ok
test v2_importierte_deklarationen_behalten_kontext_und_abhaengigkeiten ... ok
test common_arrays_behalten_dynamische_grenzen_und_kompatible_offene_deklarationen ... ok
test importkontext_bleibt_auch_bei_abweichenden_lokalen_definitionen_erhalten ... ok
test schleifengrenzen_tragen_breakpoint_include_und_resume ... ok
test result: ok. 16 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running tests/vm.rs (target/debug/deps/vm-4bcfa272723580f5)
running 114 tests
test data_read_restore ... ok
test doevents_liefert_null ... ok
test data_typkonflikt_fehler_13 ... ok
test byref_wirkt_zurueck ... ok
test byref_auf_arrayelement ... ok
test default_button_hat_vorrang_und_beendet_modales_formular ... ok
test color_prueft_wertebereich ... ok
test banker_rounding_cint ... ok
test common_dialog_registrierung_setzt_byref_erfolg ... ok
test clipboard_und_printer_objekte_haben_laufzeitwirkung ... ok
test breakpoint_haelt_an_und_setzt_fort ... ok
test division_durch_null ... ok
test ausgabe_erscheint_vor_der_eingabe ... ok
test def_fn_im_modulkontext ... ok
test cls_setzt_cursor_zurueck ... ok
test einzelschritt ... ok
test do_loop_varianten ... ok
test eingabe_wird_geechot_und_rueckschritt_loescht ... ok
test doevents_stellt_zu ... ok
test erl_liefert_zeilennummer ... ok
test erl_null_ohne_zeilennummern ... ok
test end_und_system ... ok
test erl_nennt_die_fehlerzeile_nicht_die_handlerzeile ... ok
test error_anweisung ... ok
test exit_for_und_do ... ok
test fehlende_ereignisprozedur_verfaellt_und_typeof_prueft_klasse ... ok
test for_mit_dynamischem_step ... ok
test fester_versatz_verschiebt_die_uhr ... ok
test for_mit_negativem_step ... ok
test for_ohne_durchlauf ... ok
test fehler_im_handler_ist_fatal ... ok
test fehler_im_trap_handler_erreicht_den_modulweiten_handler ... ok
test event_off_klammert_die_zustellung ... ok
test form_load_laeuft_vor_dem_sichtbarsetzen ... ok
test grafik_line_paint_und_view_zeichnen_in_den_zellenpuffer ... ok
test inkey_ohne_taste_ist_leer ... ok
test ganzes_array_uebergeben ... ok
test inkey_liefert_zeichen_und_sondertaste ... ok
test form_load_laeuft_einmal_und_unload_cancel_verhindert_entladen ... ok
test gosub_return_und_fehler_3 ... ok
test input_mit_redo ... ok
test groessenaenderung_wirkt_zur_laufzeit ... ok
test gosub_null_schaltet_den_trap_ab ... ok
test input_s_liest_genau_n_zeichen_ohne_echo ... ok
test function_und_rekursion ... ok
test intdiv_rundet_operanden_vor ... ok
test gemischte_arithmetik ... ok
test kennung_ausserhalb_des_bereichs_ist_fehler_5 ... ok
test klammern_erzwingen_byval ... ok
test len_liefert_die_feste_satzbreite_eines_udt ... ok
test line_input_liest_ganze_zeile ... ok
test input_s_liest_aus_einer_binaerdatei ... ok
test input_s_teilt_sondertasten_und_bewahrt_rest_fuer_inkey ... ok
test klassischer_timer_trap_ruht_solange_das_menu_offen_ist ... ok
test logik_bitweise ... ok
test kontrollfluss_korpusdatei ... ok
test listenmethoden_und_indexeigenschaft_laufen_auf_control_arrays ... ok
test leere_warteschleife_hat_eine_anweisungsgrenze ... ok
test mid_anweisung_mutiert ... ok
test listenmethoden_indexeigenschaft_und_picture_messung_laufen_in_der_vm ... ok
test lokaler_handler_verdeckt_modulweiten ... ok
test locate_ausserhalb_loest_fehler_5_aus ... ok
test lokaler_handler_hat_vorrang ... ok
test mausargumente_kommen_in_zellen_an ... ok
test modales_show_setzt_nach_unload_fort ... ok
test modulweiter_handler_faengt_prozedurfehler ... ok
test ohne_handler_bricht_ab ... ok
test objektcode_erzeugt_keine_zusaetzlichen_zustellopcodes ... ok
test alle_mk_cv_paare_verlangen_exakte_bytezeichenbreite ... ok
test modulweites_on_error_aus_prozedur ... ok
test on_error_goto_0_deaktiviert ... ok
test objektzugriff_laeuft_ueber_objekt_und_eigenschaftsindex ... ok
test modelloses_formular_bleibt_nach_programmende_bedienbar ... ok
test on_goto_berechnet ... ok
test out_of_data_fehler_4 ... ok
test overflow_bei_zuweisung ... ok
test on_local_error_bleibt_prozedurlokal ... ok
test ohne_timer_on_kein_trap ... ok
test print_hallo_welt ... ok
test programmversatz_wirkt_auf_alle_zeitfunktionen ... ok
test printform_schreibt_das_textformular_in_den_druckerkanal ... ok
test restore_mit_label ... ok
test resume_ohne_fehler_20 ... ok
test print_zonen_und_tab ... ok
test resume_next_faehrt_fort ... ok
test datei_und_elternpfadfehler_sind_am_sprachzugang_unterscheidbar ... ok
test msgbox_und_inputbox_sind_keine_unsupported_opcodes_mehr ... ok
test resume_wiederholt_anweisung ... ok
test rueckfall_auf_utc_laeuft_fehlerfrei ... ok
test run_gibt_datei_oder_zeile_an_den_runner_weiter ... ok
test screen_controlpanel_hat_einen_indexierten_laufzeitwert ... ok
test select_case_bereiche ... ok
test setuevent_loest_den_uevent_trap_aus ... ok
test screen_anweisung_bildet_grafikmodi_auf_den_zellenpuffer_ab ... ok
test screen_funktion_ausserhalb_loest_fehler_5_aus ... ok
test sleep_endet_durch_ein_ereignis ... ok
test stop_liefert_zeile_und_ist_fortsetzbar ... ok
test sondertastenrest_wird_nicht_erneut_getrappt ... ok
test static_behaelt_werte ... ok
test stringvergleich_und_verkettung ... ok
test time_und_now_stimmen_ueberein ... ok
test steuerarray_ereignis_stellt_index_voran ... ok
test timer_zaehlt_ab_lokaler_mitternacht ... ok
test steuerarray_eigenschaften_und_objektparameter_funktionieren ... ok
test udt_wertsemantik_und_felder ... ok
test width_hebt_die_groesse_an ... ok
test timezoneknown_hat_integer_signatur_und_zeigt_aktuellen_status ... ok
test timer_off_im_handler_ueberlebt_das_return ... ok
test trap_aus_einer_sub_heraus_kehrt_dorthin_zurueck ... ok
test input_datei_bewahrt_felder_und_unterscheidet_leer_von_fehlend ... ok
test zeit_trap_laeuft_und_kehrt_zurueck ... ok
test input_datei_alle_lesearten_teilen_utf8_cursor_und_seek ... ok
test unicode_puffergrenzen_und_binaere_records_bleiben_getrennt ... ok
test open_for_isam_endet_nicht_mehr_mit_fehler_73 ... ok
test result: ok. 114 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
Doc-tests tb_export
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_frontend
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_ide
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_runner
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_runtime
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_ui
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Doc-tests tb_vm
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

View File

@@ -0,0 +1,3 @@
PASS {'shell_exit': 7}
PASS {'abort': True}
PASS {'file_shell': True}

View File

@@ -0,0 +1 @@
{"target": "aarch64-apple-darwin", "tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df", "native_execution": true, "source_free": true, "run_reset": true, "output": " 1 \n 7 3 9 1 \nTB \n"}

View File

@@ -0,0 +1,13 @@
PASS native console
PASS native restart
PASS native stop
PASS native error
PASS native project
PASS native form
PASS native external
PASS PTY Eingabe, Abbruch und Wiederherstellung (CLI/native)
PASS Exportabbruch SIGINT/SIGTERM, Original und Bereinigung
PASS corrupted length
PASS corrupted version
PASS corrupted truncated
Native Abnahme bestanden: aarch64-apple-darwin

View File

@@ -0,0 +1,44 @@
{
"scope": "automated-target-checks",
"started_utc": "2026-09-07T18:44:13.277828+00:00",
"host_target": "aarch64-apple-darwin",
"os_version": "macOS-26.6.2-arm64-arm-64bit-Mach-O",
"python_version": "3.14.7",
"checkout_revision": "5aa920b768a2ad29ed17c5da137e54675a7292d6",
"working_tree_dirty": true,
"working_tree_diff_sha256": "cd170b3c9923de226676c6e12a6644feaca40e048d3f750ac8935c8a2732d34d",
"program_sha256": {
"tb": "77b14be900039a72c2633f157c7deb2ea0c7c70d550e7b6a9490a38b996c5c5a",
"tbc": "a51b678bca795b88fe92a9f8ce0fcdd0f488931f85fd7f1d2721c17ed665a1e9",
"tbrt": "995a8e62454b7c4e6af2782007a4e1eda95deaca25cbacf4f3e01ac1c41ccce1",
"tb-template": "a9d73ea8002b8f01bbb1fdc033a2595605ceb3b8c55d4531bf00da236351ccbf"
},
"tbl_sha256": "5d25c2f8c95ae535e55a6c84c1ddd0d964358863f4f3c780c50ac4a2eb3594df",
"manual_terminal_status": "not_run",
"matrix_complete": false,
"checks": {
"library": {
"status": "passed",
"exit_code": 0
},
"native": {
"status": "passed",
"exit_code": 0
},
"ide-pty": {
"status": "passed",
"exit_code": 0
},
"artifact-stability": {
"status": "passed"
}
},
"harness_sha256": {
"library-abnahme.py": "118de32bc5a9cbb254b234b420455f75961603afb16aa94333be8c4b5b17e962",
"platform-abnahme.py": "a5eadbd4bcabaae6c3c173ce933f83e331d03b1cf22998fe564519fe8f6b9696",
"native-abnahme.py": "fe9f34bcc581e6230909b6277bae4768dbf6be068e4616b47aaff4390424ca14",
"release-abnahme.py": "f77b1481913050faa321f9978d0f6cf31e8f884a1f626a861727b5393e1bea83",
"ide-execution-pty.py": "751afaadc1f25639eab7d5f746bee9ad0a5c11446bab8dbd4ccdd6dfae6fe4f9"
},
"all_automated_passed": true
}

View File

@@ -0,0 +1,2 @@
1: Key(KeyEvent { code: Char('x'), modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })
2: Key(KeyEvent { code: Esc, modifiers: KeyModifiers(0x0), kind: Press, state: KeyEventState(0x0) })

View File

@@ -0,0 +1,8 @@
M crates/tb-ide/tests/execution.rs
M crates/tb-ui/examples/spike.rs
M crates/tb-ui/src/terminal.rs
M tests/support/ide-execution-pty.py
?? docs/plattformmatrix.md
?? tests/platform/
?? tests/support/__pycache__/
?? tests/support/platform-abnahme.py

View File

@@ -0,0 +1,452 @@
diff --git a/crates/tb-ide/tests/execution.rs b/crates/tb-ide/tests/execution.rs
index 237ac71..e92a0b6 100644
--- a/crates/tb-ide/tests/execution.rs
+++ b/crates/tb-ide/tests/execution.rs
@@ -530,3 +530,35 @@ fn comment_only_source_change_also_requires_revision_choice() {
DialogKind::ResumeRevision
));
}
+
+#[test]
+fn platform_probe_records_each_basic_key_once_and_keeps_ide_keys_out() {
+ let t = Temp::new();
+ let log = t.0.join("platform-events.log");
+ let source = include_str!("../../../tests/platform/input.frm")
+ .replace("platform-events.log", &log.to_string_lossy());
+ let path = t.0.join("input.frm");
+ fs::write(&path, source).unwrap();
+ let mut a = t.app("");
+ a.load_initial_project(path).unwrap();
+ a.execute(Command::Start);
+ tick(&mut a);
+ key(&mut a, K::Char('a'), M::NONE);
+ tick(&mut a);
+ a.handle(Event::Key(KeyEvent::new_with_kind(
+ K::Char('a'),
+ M::NONE,
+ crossterm::event::KeyEventKind::Release,
+ )));
+ tick(&mut a);
+ let events = fs::read_to_string(&log).unwrap();
+ assert_eq!(
+ events.replace('\r', ""),
+ "Down: 97 : 0 \nPress: 97 : 0 \nUp: 97 : 0 \n"
+ );
+ edit_window(&mut a);
+ key(&mut a, K::F(1), M::NONE);
+ tick(&mut a);
+ assert_eq!(fs::read_to_string(&log).unwrap(), events);
+ assert!(matches!(a.active_window().unwrap().kind, WindowKind::Help));
+}
diff --git a/crates/tb-ui/examples/spike.rs b/crates/tb-ui/examples/spike.rs
index b6b5ff3..38196ed 100644
--- a/crates/tb-ui/examples/spike.rs
+++ b/crates/tb-ui/examples/spike.rs
@@ -1,15 +1,12 @@
//! Phase-0-Spike: 80×25-Bildschirmpuffer, 16-Farben-Palette, Unicode,
//! Tastatur- und Mausereignisse.
//!
-//! Start: `cargo run -p tb-ui --example spike` — Beenden mit Esc.
+//! Start: `cargo run -p tb-ui --features terminal --example spike -- --log events.txt` — Beenden mit Esc.
//! Getippte Zeichen erscheinen im Eingabebereich; Sondertasten und
//! Mausereignisse werden in der Statuszeile angezeigt.
-use crossterm::event::{
- self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEventKind,
-};
-use crossterm::execute;
-use std::io::stdout;
+use crossterm::event::{self, Event, KeyCode, KeyEventKind};
+use std::io::{stdout, Write};
use std::time::Duration;
use tb_ui::screen::{ScreenWidget, TextScreen};
@@ -57,8 +54,24 @@ fn status(s: &mut TextScreen, text: &str) {
}
fn main() -> anyhow::Result<()> {
- let mut terminal = ratatui::init();
- execute!(stdout(), EnableMouseCapture)?;
+ let args: Vec<_> = std::env::args_os().skip(1).collect();
+ anyhow::ensure!(
+ args.is_empty() || (args.len() == 2 && args[0] == "--log"),
+ "Aufruf: spike [--log neue-datei]"
+ );
+ let mut log = if args.len() == 2 {
+ Some(
+ std::fs::OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&args[1])?,
+ )
+ } else {
+ None
+ };
+ let _guard = tb_ui::terminal::TerminalGuard::enter(stdout())?;
+ let mut terminal = ratatui::Terminal::new(ratatui::backend::CrosstermBackend::new(stdout()))?;
+ let mut sequence = 0u64;
let (tw, th) = crossterm::terminal::size()?;
let mut screen = TextScreen::with_size(tw as usize, th as usize);
@@ -70,7 +83,12 @@ fn main() -> anyhow::Result<()> {
if !event::poll(Duration::from_millis(100))? {
continue;
}
- match event::read()? {
+ let received = event::read()?;
+ sequence += 1;
+ if let Some(log) = &mut log {
+ writeln!(log, "{sequence}: {received:?}")?;
+ }
+ match received {
Event::Key(k) if k.kind != KeyEventKind::Release => match k.code {
KeyCode::Esc => break,
KeyCode::Char(c) => {
@@ -105,7 +123,5 @@ fn main() -> anyhow::Result<()> {
}
}
- execute!(stdout(), DisableMouseCapture)?;
- ratatui::restore();
Ok(())
}
diff --git a/crates/tb-ui/src/terminal.rs b/crates/tb-ui/src/terminal.rs
index 77493b2..a8204d4 100644
--- a/crates/tb-ui/src/terminal.rs
+++ b/crates/tb-ui/src/terminal.rs
@@ -11,23 +11,44 @@ use std::io::{self, Write};
pub struct TerminalGuard<W: Write> {
writer: W,
raw: fn(bool) -> io::Result<()>,
+ #[cfg(windows)]
+ modes: Option<ConsoleModes>,
}
impl<W: Write> TerminalGuard<W> {
pub fn enter(writer: W) -> io::Result<Self> {
- Self::with_raw(writer, |on| {
+ #[cfg(windows)]
+ let modes = ConsoleModes::capture()?;
+ #[cfg(windows)]
+ modes.enable_output()?;
+ #[allow(unused_mut)]
+ let mut guard = Self::with_raw(writer, |on| {
if on {
enable_raw_mode()
} else {
disable_raw_mode()
}
- })
+ })?;
+ #[cfg(windows)]
+ {
+ guard.modes = Some(modes);
+ }
+ Ok(guard)
}
fn with_raw(writer: W, raw: fn(bool) -> io::Result<()>) -> io::Result<Self> {
- let mut guard = Self { writer, raw };
+ let mut guard = Self {
+ writer,
+ raw,
+ #[cfg(windows)]
+ modes: None,
+ };
guard.resume()?;
Ok(guard)
}
fn resume(&mut self) -> io::Result<()> {
+ #[cfg(windows)]
+ if let Some(modes) = &self.modes {
+ modes.enable_output()?;
+ }
(self.raw)(true)?;
execute!(
self.writer,
@@ -38,15 +59,18 @@ impl<W: Write> TerminalGuard<W> {
)
}
fn suspend(&mut self) -> io::Result<()> {
- (self.raw)(false)?;
- execute!(
+ let raw = (self.raw)(false);
+ let output = execute!(
self.writer,
ResetColor,
Show,
DisableMouseCapture,
DisableBracketedPaste,
LeaveAlternateScreen
- )
+ );
+ #[cfg(windows)]
+ let output = output.and(self.modes.as_ref().map_or(Ok(()), ConsoleModes::restore));
+ raw.and(output)
}
/// Foreground child owns cooked terminal input. Always restore the IDE,
/// including spawn/wait errors and a child interrupted by Ctrl+C.
@@ -91,6 +115,10 @@ impl<W: Write> TerminalGuard<W> {
}
impl<W: Write> Drop for TerminalGuard<W> {
fn drop(&mut self) {
+ #[cfg(windows)]
+ if let Some(modes) = &self.modes {
+ let _ = modes.enable_output();
+ }
let _ = (self.raw)(false);
let _ = execute!(
self.writer,
@@ -103,6 +131,58 @@ impl<W: Write> Drop for TerminalGuard<W> {
}
}
+// Crossterm stellt unter Windows Raw-/Mausflags pauschal zurück. Der Besitzer
+// muss zusätzlich die tatsächlichen Eingabe- und Ausgabemodi wiederherstellen.
+#[cfg(windows)]
+#[derive(Debug)]
+struct ConsoleModes([(std::os::windows::io::RawHandle, u32); 2]);
+#[cfg(windows)]
+#[link(name = "kernel32")]
+extern "system" {
+ fn GetStdHandle(kind: u32) -> std::os::windows::io::RawHandle;
+ fn GetConsoleMode(handle: std::os::windows::io::RawHandle, mode: *mut u32) -> i32;
+ fn SetConsoleMode(handle: std::os::windows::io::RawHandle, mode: u32) -> i32;
+}
+#[cfg(windows)]
+impl ConsoleModes {
+ fn capture() -> io::Result<Self> {
+ let mut modes = [(std::ptr::null_mut(), 0); 2];
+ for (slot, kind) in modes.iter_mut().zip([(-10i32) as u32, (-11i32) as u32]) {
+ // Borrowed standard handles: valid until the terminal owner exits.
+ slot.0 = unsafe { GetStdHandle(kind) };
+ if unsafe { GetConsoleMode(slot.0, &mut slot.1) } == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ }
+ Ok(Self(modes))
+ }
+ fn enable_output(&self) -> io::Result<()> {
+ // Crossterm caches ANSI support, but a Shell handoff restores the old
+ // output flags. Re-enable VT for each ownership cycle, including re-entry.
+ if crossterm::ansi_support::supports_ansi()
+ && unsafe { SetConsoleMode(self.0[1].0, self.0[1].1 | 0x0004) } == 0
+ {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(())
+ }
+ fn restore(&self) -> io::Result<()> {
+ let mut result = Ok(());
+ for (handle, mode) in self.0 {
+ if unsafe { SetConsoleMode(handle, mode) } == 0 && result.is_ok() {
+ result = Err(io::Error::last_os_error());
+ }
+ }
+ result
+ }
+}
+#[cfg(windows)]
+impl Drop for ConsoleModes {
+ fn drop(&mut self) {
+ let _ = self.restore();
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -149,7 +229,7 @@ mod tests {
("exit 7", false, 7),
(
if cfg!(windows) {
- "ping -n 30 127.0.0.1 >nul"
+ "for /L %i in (1,1,1000000000) do @rem"
} else {
"exec sleep 30"
},
@@ -160,10 +240,13 @@ mod tests {
let out = Output(Rc::default());
let copy = out.clone();
let mut guard = TerminalGuard::with_raw(out, raw).unwrap();
- assert_eq!(
- guard.shell_with_interrupt(command, || abort).unwrap(),
- expected
- );
+ let started = std::time::Instant::now();
+ let code = guard.shell_with_interrupt(command, || abort).unwrap();
+ if !abort {
+ assert_eq!(code, expected);
+ }
+ // Windows TerminateProcess liefert einen Code; Unix ein Signal.
+ assert!(started.elapsed() < std::time::Duration::from_secs(5));
assert_eq!(RAW.load(Ordering::SeqCst), 1);
let text = String::from_utf8(copy.0.borrow().clone()).unwrap();
assert_eq!(text.matches("?1049h").count(), 2);
@@ -187,4 +270,111 @@ mod tests {
.unwrap()
.contains("?25h"));
}
+ /// Run explicitly on Windows; never counts as a Windows Terminal UI test.
+ #[cfg(windows)]
+ #[test]
+ #[ignore = "benötigt Windows mit echter Konsole; isolierter Kindprozess"]
+ fn windows_console_restores_modes() {
+ use std::os::windows::{io::AsRawHandle, process::CommandExt};
+ use std::process::{Command, Stdio};
+ use std::time::{Duration, Instant};
+ const CHILD: &str = "TB_CONSOLE_TEST_CHILD";
+ if std::env::var_os(CHILD).is_none() {
+ let mut child = Command::new(std::env::current_exe().unwrap())
+ .args([
+ "--exact",
+ "terminal::tests::windows_console_restores_modes",
+ "--ignored",
+ "--nocapture",
+ ])
+ .env(CHILD, "1")
+ .creation_flags(0x00000010) // CREATE_NEW_CONSOLE: never touches the caller's console.
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .spawn()
+ .unwrap();
+ let deadline = Instant::now() + Duration::from_secs(30);
+ loop {
+ if let Some(status) = child.try_wait().unwrap() {
+ assert!(
+ status.success(),
+ "Isolierte Windows-Konsolenprüfung: {status}"
+ );
+ return;
+ }
+ if Instant::now() >= deadline {
+ let _ = child.kill();
+ let _ = child.wait();
+ panic!("Windows-Konsolenprüfung hat die Frist überschritten");
+ }
+ std::thread::sleep(Duration::from_millis(20));
+ }
+ }
+ #[link(name = "kernel32")]
+ extern "system" {
+ fn SetStdHandle(kind: u32, handle: std::os::windows::io::RawHandle) -> i32;
+ }
+ let open = |name| {
+ std::fs::OpenOptions::new()
+ .read(true)
+ .write(true)
+ .open(name)
+ .unwrap()
+ };
+ let input = open("CONIN$");
+ let output = open("CONOUT$");
+ // Standard streams started as NUL; bind to this child's own new console.
+ assert_ne!(
+ unsafe { SetStdHandle((-10i32) as u32, input.as_raw_handle()) },
+ 0
+ );
+ assert_ne!(
+ unsafe { SetStdHandle((-11i32) as u32, output.as_raw_handle()) },
+ 0
+ );
+ let original = ConsoleModes::capture().unwrap();
+ // Non-default echo flags catch a reset-to-default masquerading as restore.
+ let mode = original.0[0].1 & !0x0004; // ENABLE_ECHO_INPUT
+ assert_ne!(unsafe { SetConsoleMode(input.as_raw_handle(), mode) }, 0);
+ let expected = ConsoleModes::capture().unwrap();
+ let assert_restored = || assert_eq!(ConsoleModes::capture().unwrap().0, expected.0);
+ for (command, abort, code) in [
+ ("exit 0", false, 0),
+ ("exit 7", false, 7),
+ ("for /L %i in (1,1,1000000000) do @rem", true, 0),
+ ] {
+ let mut guard = TerminalGuard::enter(io::stdout()).unwrap();
+ guard.suspend().unwrap();
+ assert_restored();
+ guard.resume().unwrap();
+ if crossterm::ansi_support::supports_ansi() {
+ assert_ne!(ConsoleModes::capture().unwrap().0[1].1 & 0x0004, 0);
+ }
+ let result = guard.shell_with_interrupt(command, || abort).unwrap();
+ if !abort {
+ assert_eq!(result, code);
+ }
+ assert!(crossterm::terminal::is_raw_mode_enabled().unwrap());
+ drop(guard);
+ assert_restored();
+ }
+ let error = (|| -> io::Result<()> {
+ let _guard = TerminalGuard::enter(io::stdout())?;
+ Err(io::Error::other("Erzwungener Renderfehler"))
+ })();
+ assert!(error.is_err());
+ assert_restored();
+ struct Broken;
+ impl Write for Broken {
+ fn write(&mut self, _: &[u8]) -> io::Result<usize> {
+ Err(io::Error::other("Initialisierung"))
+ }
+ fn flush(&mut self) -> io::Result<()> {
+ Ok(())
+ }
+ }
+ assert!(TerminalGuard::enter(Broken).is_err());
+ assert_restored();
+ }
}
diff --git a/tests/support/ide-execution-pty.py b/tests/support/ide-execution-pty.py
index b422070..d43c472 100644
--- a/tests/support/ide-execution-pty.py
+++ b/tests/support/ide-execution-pty.py
@@ -2,6 +2,7 @@
"""Headless Unix PTY smoke: real terminal handoff, child Ctrl+C and IDE resume.
Run after cargo build -p tb-ide: python3 tests/support/ide-execution-pty.py
"""
+import argparse
import fcntl
import os
from pathlib import Path
@@ -13,10 +14,10 @@ import tempfile
import termios
import time
-binary = Path(__file__).resolve().parents[2] / 'target/debug/tb'
-def exercise(abort=False, file_shell=False):
+
+def exercise(binary, transcript_dir=None, abort=False, file_shell=False):
with tempfile.TemporaryDirectory(prefix='tb-pty-') as directory:
source = Path(directory) / 'main.bas'
command = "printf '\\nWAITING_CHILD\\n'; exec sleep 30" if abort else "printf '\\nSHELL_PROOF\\n'; exit 7"
@@ -84,6 +85,9 @@ def exercise(abort=False, file_shell=False):
assert transcript.count(b'?1049h') >= 2
assert transcript.count(b'?1049l') >= 2
finally:
+ if transcript_dir:
+ name = 'file-shell' if file_shell else 'shell-abort' if abort else 'shell-exit'
+ (transcript_dir / (name + '.ansi')).write_bytes(transcript)
if not reaped:
os.killpg(pid, signal.SIGKILL)
os.waitpid(pid, 0)
@@ -91,6 +95,18 @@ def exercise(abort=False, file_shell=False):
os.close(slave)
-for kwargs in ({}, {'abort': True}, {'file_shell': True}):
- exercise(**kwargs)
- print('PASS', kwargs or {'shell_exit': 7})
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('--binary', type=Path, default=Path(__file__).resolve().parents[2] / 'target/debug/tb')
+ parser.add_argument('--transcript-dir', type=Path)
+ args = parser.parse_args()
+ binary = args.binary.resolve(strict=True)
+ if args.transcript_dir:
+ args.transcript_dir.mkdir(parents=True, exist_ok=True)
+ for kwargs in ({}, {'abort': True}, {'file_shell': True}):
+ exercise(binary, args.transcript_dir, **kwargs)
+ print('PASS', kwargs or {'shell_exit': 7})
+
+
+if __name__ == '__main__':
+ main()