Skip to content

Commit bc54f44

Browse files
committed
TextFrame(fix[display]): Use shutil for terminal size detection
why: curses KEY_RESIZE only fires on getch(), missing resize events when terminal is resized but no key is pressed what: - Replace stdscr.getmaxyx() with shutil.get_terminal_size() - Remove KEY_RESIZE handling (now redundant) - Update test to verify shutil.get_terminal_size() is called This follows Rich's approach: query terminal size directly via ioctl(TIOCGWINSZ) on each loop iteration, which works reliably in tmux and other terminal multiplexers.
1 parent 82a22f5 commit bc54f44

File tree

2 files changed

+21
-14
lines changed

2 files changed

+21
-14
lines changed

src/libtmux/textframe/core.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import contextlib
1010
import curses
11+
import shutil
1112
import sys
1213
import typing as t
1314
from dataclasses import dataclass, field
@@ -250,7 +251,10 @@ def _curses_display(self, stdscr: curses.window) -> None:
250251

251252
while True:
252253
stdscr.clear()
253-
max_y, max_x = stdscr.getmaxyx()
254+
255+
# Query terminal size directly (handles resize without signals)
256+
term_size = shutil.get_terminal_size()
257+
max_x, max_y = term_size.columns, term_size.lines
254258

255259
# Calculate scroll bounds
256260
max_scroll_y = max(0, len(lines) - max_y + 1)
@@ -311,7 +315,3 @@ def _curses_display(self, stdscr: curses.window) -> None:
311315
scroll_y = 0
312316
elif key == curses.KEY_END:
313317
scroll_y = max_scroll_y
314-
315-
# Terminal resize
316-
elif key == curses.KEY_RESIZE:
317-
curses.resize_term(*stdscr.getmaxyx())

tests/textframe/test_display.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,19 +138,26 @@ def test_status_line_displayed(self, mock_curses: None) -> None:
138138
]
139139
assert len(status_calls) > 0, "Status line should be displayed"
140140

141-
def test_terminal_resize_handling(self, mock_curses: None) -> None:
142-
"""Verify terminal resize events are handled via KEY_RESIZE."""
143-
import curses as curses_module
141+
def test_terminal_resize_via_shutil(self, mock_curses: None) -> None:
142+
"""Verify terminal size is queried via shutil.get_terminal_size().
143+
144+
This approach works reliably in tmux/multiplexers because it directly
145+
queries the terminal via ioctl(TIOCGWINSZ) on each loop iteration,
146+
rather than relying on curses KEY_RESIZE events.
147+
"""
148+
import os
144149

145150
frame = TextFrame(content_width=10, content_height=2)
146151
frame.set_content(["hello", "world"])
147152

148153
mock_stdscr = MagicMock()
149-
# Start with 24x80, resize to 40x120, then quit
150-
mock_stdscr.getmaxyx.side_effect = [(24, 80), (40, 120), (40, 120)]
151-
mock_stdscr.getch.side_effect = [curses_module.KEY_RESIZE, ord("q")]
154+
mock_stdscr.getch.return_value = ord("q")
152155

153-
with patch("curses.resize_term") as mock_resize:
156+
# Mock shutil.get_terminal_size to return specific dimensions
157+
with patch(
158+
"libtmux.textframe.core.shutil.get_terminal_size",
159+
return_value=os.terminal_size((120, 40)),
160+
) as mock_get_size:
154161
frame._curses_display(mock_stdscr)
155-
# Verify resize_term was called with new dimensions
156-
mock_resize.assert_called_once_with(40, 120)
162+
# Verify shutil.get_terminal_size was called
163+
mock_get_size.assert_called()

0 commit comments

Comments
 (0)