Skip to content

Commit c043d03

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 e0a047e commit c043d03

File tree

2 files changed

+29
-1
lines changed

2 files changed

+29
-1
lines changed

src/libtmux/textframe/core.py

Lines changed: 5 additions & 1 deletion
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)

tests/textframe/test_display.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,27 @@ def test_status_line_displayed(self, mock_curses: None) -> None:
137137
if len(call[0]) >= 3 and "q:quit" in str(call[0][2])
138138
]
139139
assert len(status_calls) > 0, "Status line should be displayed"
140+
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
149+
150+
frame = TextFrame(content_width=10, content_height=2)
151+
frame.set_content(["hello", "world"])
152+
153+
mock_stdscr = MagicMock()
154+
mock_stdscr.getch.return_value = ord("q")
155+
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:
161+
frame._curses_display(mock_stdscr)
162+
# Verify shutil.get_terminal_size was called
163+
mock_get_size.assert_called()

0 commit comments

Comments
 (0)