@@ -118,6 +118,7 @@ struct public TerminalBufferSnapshot {
118118 scrollback : array<TerminalCell>
119119 scrollback_rows : int
120120 cursor : TerminalCursor = TerminalCursor()
121+ saved_cursor : TerminalCursor = TerminalCursor()
121122}
122123
123124struct public TerminalModes {
@@ -1184,6 +1185,7 @@ def private collect_buffer(buffer : TerminalBuffer const; columns, rows : int;
11841185 }
11851186 snapshot .scrollback_rows = length (buffer .history )
11861187 snapshot .cursor = buffer .cursor
1188+ snapshot .saved_cursor = buffer .saved_cursor
11871189}
11881190
11891191def public terminal_snapshot (terminal : Terminal const ; var snapshot : TerminalSnapshot ) {
@@ -1365,6 +1367,175 @@ def private same_color(left, right : TerminalColor const) : bool {
13651367 left .red == right .red && left .green == right .green && left .blue == right .blue )
13661368}
13671369
1370+ def private checkpoint_same_style (left , right : TerminalCell const ) : bool {
1371+ return (left .attributes == right .attributes &&
1372+ same_color (left .foreground , right .foreground ) &&
1373+ same_color (left .background , right .background ))
1374+ }
1375+
1376+ def private checkpoint_write_color (var writer : StringBuilderWriter ;
1377+ color : TerminalColor const ;
1378+ foreground : bool ) {
1379+ let base = foreground ? 38 : 48
1380+ if (color .kind == TerminalColorKind .indexed ) {
1381+ writer | > write (";{ base } ;5;{ color .index } " )
1382+ } elif (color .kind == TerminalColorKind .rgb ) {
1383+ writer | > write (";{ base } ;2;{ color .red } ;{ color .green } ;{ color .blue } " )
1384+ }
1385+ }
1386+
1387+ def private checkpoint_write_style (var writer : StringBuilderWriter ;
1388+ cell : TerminalCell const ) {
1389+ let esc = to_char (27 )
1390+ writer | > write ("{ esc } [0" )
1391+ if ((cell .attributes & TERMINAL_ATTR_BOLD ) ! = 0 ) writer | > write (";1" )
1392+ if ((cell .attributes & TERMINAL_ATTR_FAINT ) ! = 0 ) writer | > write (";2" )
1393+ if ((cell .attributes & TERMINAL_ATTR_ITALIC ) ! = 0 ) writer | > write (";3" )
1394+ if ((cell .attributes & TERMINAL_ATTR_UNDERLINE ) ! = 0 ) writer | > write (";4" )
1395+ if ((cell .attributes & TERMINAL_ATTR_BLINK ) ! = 0 ) writer | > write (";5" )
1396+ if ((cell .attributes & TERMINAL_ATTR_INVERSE ) ! = 0 ) writer | > write (";7" )
1397+ if ((cell .attributes & TERMINAL_ATTR_HIDDEN ) ! = 0 ) writer | > write (";8" )
1398+ if ((cell .attributes & TERMINAL_ATTR_STRIKE ) ! = 0 ) writer | > write (";9" )
1399+ checkpoint_write_color (writer , cell .foreground , true )
1400+ checkpoint_write_color (writer , cell .background , false )
1401+ writer | > write ("m" )
1402+ }
1403+
1404+ def private checkpoint_write_hyperlink (var writer : StringBuilderWriter ;
1405+ hyperlink : string ) {
1406+ let esc = to_char (27 )
1407+ writer | > write ("{ esc } ]8;;{ hyperlink } { esc } \\ " )
1408+ }
1409+
1410+ def private checkpoint_cell_visible (cell : TerminalCell const ) : bool {
1411+ let has_text = cell .width ! = 0 && cell .grapheme ! = "" && cell .grapheme ! = " "
1412+ let has_style = (cell .attributes ! = 0 ||
1413+ cell .foreground .kind ! = TerminalColorKind .default_color ||
1414+ cell .background .kind ! = TerminalColorKind .default_color )
1415+ return has_text || has_style || ! empty (cell .hyperlink )
1416+ }
1417+
1418+ def private checkpoint_write_row (var writer : StringBuilderWriter ;
1419+ row : TerminalRow const ;
1420+ var active_style : TerminalCell ;
1421+ var active_hyperlink : string ) {
1422+ var last_column = - 1
1423+ for (column in range (length (row .cells ))) {
1424+ if (checkpoint_cell_visible (row .cells [column ])) {
1425+ last_column = column
1426+ }
1427+ }
1428+ for (column in range (last_column + 1 )) {
1429+ let cell = row .cells [column ]
1430+ if (cell .width == 0 ) continue
1431+ if (! checkpoint_same_style (active_style , cell )) {
1432+ checkpoint_write_style (writer , cell )
1433+ active_style = cell
1434+ }
1435+ if (active_hyperlink ! = cell .hyperlink ) {
1436+ checkpoint_write_hyperlink (writer , cell .hyperlink )
1437+ active_hyperlink = cell .hyperlink
1438+ }
1439+ writer | > write (empty (cell .grapheme ) ? " " : cell .grapheme )
1440+ }
1441+ }
1442+
1443+ def private checkpoint_write_buffer (var writer : StringBuilderWriter ;
1444+ buffer : TerminalBuffer const ;
1445+ include_history : bool ;
1446+ var active_style : TerminalCell ;
1447+ var active_hyperlink : string ) {
1448+ var line = 0
1449+ let line_count = (include_history ? length (buffer .history ) : 0 ) + length (buffer .screen )
1450+ if (include_history ) {
1451+ for (row in range (length (buffer .history ))) {
1452+ if (line > 0 ) writer | > write ("\r\n " )
1453+ checkpoint_write_row (writer , buffer .history [history_index (buffer , row )],
1454+ active_style , active_hyperlink )
1455+ line ++
1456+ }
1457+ }
1458+ for (row in range (length (buffer .screen ))) {
1459+ if (line > 0 ) writer | > write ("\r\n " )
1460+ checkpoint_write_row (writer , buffer .screen [screen_index (buffer , row )],
1461+ active_style , active_hyperlink )
1462+ line ++
1463+ }
1464+ assert (line == line_count )
1465+ }
1466+
1467+ def private checkpoint_write_mode (var writer : StringBuilderWriter ;
1468+ mode : int ; enabled : bool ) {
1469+ let esc = to_char (27 )
1470+ let suffix = enabled ? "h" : "l"
1471+ writer | > write ("{ esc } [?{ mode } { suffix } " )
1472+ }
1473+
1474+ def private checkpoint_write_cursor_state (var writer : StringBuilderWriter ;
1475+ cursor : TerminalCursor const ) {
1476+ let esc = to_char (27 )
1477+ writer | > write ("{ esc } [{ cursor .style } q" )
1478+ checkpoint_write_mode (writer , 25 , cursor .visible )
1479+ }
1480+
1481+ def private checkpoint_restore_buffer_state (var writer : StringBuilderWriter ;
1482+ buffer : TerminalBuffer const ;
1483+ var active_style : TerminalCell ;
1484+ var active_hyperlink : string ) {
1485+ let esc = to_char (27 )
1486+ writer | > write ("{ esc } [{ buffer .scroll_top + 1 } ;{ buffer .scroll_bottom + 1 } r" )
1487+ writer | > write ("{ esc } [{ buffer .saved_cursor .row + 1 } ;{ buffer .saved_cursor .column + 1 } H" )
1488+ checkpoint_write_cursor_state (writer , buffer .saved_cursor )
1489+ writer | > write ("{ esc } [s" )
1490+ writer | > write ("{ esc } [{ buffer .cursor .row + 1 } ;{ buffer .cursor .column + 1 } H" )
1491+ checkpoint_write_cursor_state (writer , buffer .cursor )
1492+ checkpoint_write_style (writer , buffer .pen )
1493+ active_style = buffer .pen
1494+ if (active_hyperlink ! = buffer .pen .hyperlink ) {
1495+ checkpoint_write_hyperlink (writer , buffer .pen .hyperlink )
1496+ active_hyperlink = buffer .pen .hyperlink
1497+ }
1498+ }
1499+
1500+ //! Serializes authoritative terminal state so a fresh emulator can resume without replaying TUI history.
1501+ def public terminal_checkpoint_ansi (terminal : Terminal const ) : string {
1502+ let esc = to_char (27 )
1503+ return build_string () $ (var writer) {
1504+ var active_style = TerminalCell ()
1505+ var active_hyperlink = ""
1506+ writer | > write ("{ esc } c{ esc } [?7l{ esc } [H" )
1507+ checkpoint_write_buffer (writer , terminal .normal , true ,
1508+ active_style , active_hyperlink )
1509+ checkpoint_restore_buffer_state (writer , terminal .normal ,
1510+ active_style , active_hyperlink )
1511+ if (terminal .alternate_active ) {
1512+ checkpoint_write_mode (writer , 1049 , true )
1513+ active_style = TerminalCell ()
1514+ active_hyperlink = ""
1515+ checkpoint_write_mode (writer , 7 , false )
1516+ writer | > write ("{ esc } [H" )
1517+ checkpoint_write_buffer (writer , terminal .alternate , false ,
1518+ active_style , active_hyperlink )
1519+ checkpoint_restore_buffer_state (writer , terminal .alternate ,
1520+ active_style , active_hyperlink )
1521+ }
1522+ checkpoint_write_mode (writer , 1 , terminal .modes .application_cursor_keys )
1523+ checkpoint_write_mode (writer , 7 , terminal .modes .auto_wrap )
1524+ checkpoint_write_mode (writer , 1000 , terminal .modes .mouse_button_reporting )
1525+ checkpoint_write_mode (writer , 1002 , terminal .modes .mouse_drag_reporting )
1526+ checkpoint_write_mode (writer , 1003 , terminal .modes .mouse_any_reporting )
1527+ checkpoint_write_mode (writer , 1004 , terminal .modes .focus_reporting )
1528+ checkpoint_write_mode (writer , 1006 , terminal .modes .sgr_mouse_encoding )
1529+ checkpoint_write_mode (writer , 2004 , terminal .modes .bracketed_paste )
1530+ let buffer & = terminal .alternate_active ? terminal .alternate : terminal .normal
1531+ checkpoint_write_cursor_state (writer , buffer .cursor )
1532+ if (! empty (terminal .title )) writer | > write ("{ esc } ]2;{ terminal .title } { esc } \\ " )
1533+ if (! empty (terminal .current_directory )) {
1534+ writer | > write ("{ esc } ]7;{ terminal .current_directory } { esc } \\ " )
1535+ }
1536+ }
1537+ }
1538+
13681539def private batchable_ascii (cell : TerminalCell const ) : bool {
13691540 if (cell .width ! = 1 || cell .attributes ! = 0 || cell .hyperlink ! = "" ||
13701541 length (cell .grapheme ) ! = 1 ) return false
@@ -1541,6 +1712,7 @@ def public terminal_viewport_snapshot(terminal : Terminal const; scroll_offset :
15411712 snapshot .text_run_projection_us = get_time_usec (runs_started )
15421713 snapshot .buffer .scrollback_rows = history_rows
15431714 snapshot .buffer .cursor = buffer .cursor
1715+ snapshot .buffer .saved_cursor = buffer .saved_cursor
15441716}
15451717
15461718def public terminal_viewport_snapshot_dispose (var snapshot : TerminalViewportSnapshot ) {
0 commit comments