diff --git a/src/webterm/alt_screen.py b/src/webterm/alt_screen.py index 659294a..2269029 100644 --- a/src/webterm/alt_screen.py +++ b/src/webterm/alt_screen.py @@ -19,8 +19,10 @@ import pyte if TYPE_CHECKING: from pyte.screens import Char -# Private mode 1049 (alternate screen buffer) - shifted by 5 as per pyte's convention +# Private mode alternate screen buffers (1047/1048/1049) - shifted by 5 per pyte's convention DECALTBUF = 1049 << 5 +DECALTBUF_1047 = 1047 << 5 +DECALTBUF_1048 = 1048 << 5 class AltScreen(pyte.Screen): @@ -74,10 +76,20 @@ class AltScreen(pyte.Screen): # Mark all lines as dirty for re-render self.dirty.update(range(self.lines)) + def _is_alt_buffer_mode(self, modes: tuple[int, ...]) -> bool: + return 1047 in modes or 1048 in modes or 1049 in modes + + def _has_alt_buffer_enabled(self) -> bool: + return ( + DECALTBUF in self.mode + or DECALTBUF_1047 in self.mode + or DECALTBUF_1048 in self.mode + ) + def set_mode(self, *modes: int, **kwargs: Any) -> None: """Set (enable) modes, with special handling for alternate screen buffer.""" - # Check if we're entering alternate screen mode (private mode 1049) - if kwargs.get("private") and 1049 in modes and DECALTBUF not in self.mode: + # Check if we're entering alternate screen mode (private mode 1047/1048/1049) + if kwargs.get("private") and self._is_alt_buffer_mode(modes) and not self._has_alt_buffer_enabled(): # Save main screen before switching self._save_main_screen() # Clear screen for alternate buffer @@ -89,8 +101,8 @@ class AltScreen(pyte.Screen): def reset_mode(self, *modes: int, **kwargs: Any) -> None: """Reset (disable) modes, with special handling for alternate screen buffer.""" - # Check if we're leaving alternate screen mode (private mode 1049) - if kwargs.get("private") and 1049 in modes and DECALTBUF in self.mode: + # Check if we're leaving alternate screen mode (private mode 1047/1048/1049) + if kwargs.get("private") and self._is_alt_buffer_mode(modes) and self._has_alt_buffer_enabled(): # Will be removed by parent, restore main screen after super().reset_mode(*modes, **kwargs) self._restore_main_screen() diff --git a/tests/test_alt_screen.py b/tests/test_alt_screen.py index 7089bc8..9f95de5 100644 --- a/tests/test_alt_screen.py +++ b/tests/test_alt_screen.py @@ -1,8 +1,9 @@ """Tests for the AltScreen class with alternate screen buffer support.""" import pyte +import pytest -from webterm.alt_screen import DECALTBUF, AltScreen +from webterm.alt_screen import DECALTBUF, DECALTBUF_1047, DECALTBUF_1048, AltScreen class TestAltScreen: @@ -59,6 +60,32 @@ class TestAltScreen: stream.feed("\x1b[?1049l") assert DECALTBUF not in screen.mode + @pytest.mark.parametrize( + ("enter_seq", "exit_seq", "mode_flag"), + [ + ("\x1b[?1047h", "\x1b[?1047l", DECALTBUF_1047), + ("\x1b[?1048h", "\x1b[?1048l", DECALTBUF_1048), + ], + ) + def test_alternate_screen_mode_variants(self, enter_seq, exit_seq, mode_flag): + """Test that DECSET/DECRST 1047/1048 trigger alternate buffer handling.""" + screen = AltScreen(40, 10) + stream = pyte.Stream(screen) + + stream.feed("MAIN SCREEN\r\n") + assert "MAIN SCREEN" in screen.display[0] + + stream.feed(enter_seq) + assert mode_flag in screen.mode + assert screen.display[0].strip() == "" + + stream.feed("ALT BUFFER\r\n") + assert "ALT BUFFER" in screen.display[0] + + stream.feed(exit_seq) + assert mode_flag not in screen.mode + assert "MAIN SCREEN" in screen.display[0] + def test_multiple_alt_screen_switches(self): """Test multiple switches between main and alternate screen.""" screen = AltScreen(40, 10)