Skip to content

Commit 85df4a8

Browse files
Poll terminal size every 0.5 seconds.
Handles terminal resize events when the application runs in another thread, or on Windows where SIGWINCH can't be handled. Some benchmarking shows that this polling comes with very little cost, but does solve the issue.
1 parent b706f69 commit 85df4a8

File tree

1 file changed

+32
-1
lines changed

1 file changed

+32
-1
lines changed

prompt_toolkit/application/application.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from prompt_toolkit.buffer import Buffer
4242
from prompt_toolkit.cache import SimpleCache
4343
from prompt_toolkit.clipboard import Clipboard, InMemoryClipboard
44+
from prompt_toolkit.data_structures import Size
4445
from prompt_toolkit.enums import EditingMode
4546
from prompt_toolkit.eventloop import (
4647
get_traceback_from_context,
@@ -147,6 +148,10 @@ class Application(Generic[_AppResult]):
147148
seconds. When `None` (the default), only invalidate when `invalidate`
148149
has been called.
149150
151+
:param terminal_size_polling_interval: Poll the terminal size every so many
152+
seconds. Useful if the applications runs in a thread other then then
153+
main thread where SIGWINCH can't be handled, or on Windows.
154+
150155
Filters:
151156
152157
:param mouse_support: (:class:`~prompt_toolkit.filters.Filter` or
@@ -211,14 +216,15 @@ def __init__(
211216
min_redraw_interval: Union[float, int, None] = None,
212217
max_render_postpone_time: Union[float, int, None] = 0.01,
213218
refresh_interval: Optional[float] = None,
219+
terminal_size_polling_interval: Optional[float] = 0.5,
214220
on_reset: Optional[ApplicationEventHandler] = None,
215221
on_invalidate: Optional[ApplicationEventHandler] = None,
216222
before_render: Optional[ApplicationEventHandler] = None,
217223
after_render: Optional[ApplicationEventHandler] = None,
218224
# I/O.
219225
input: Optional[Input] = None,
220226
output: Optional[Output] = None,
221-
):
227+
) -> None:
222228

223229
# If `enable_page_navigation_bindings` is not specified, enable it in
224230
# case of full screen applications only. This can be overridden by the user.
@@ -259,6 +265,7 @@ def __init__(
259265
self.min_redraw_interval = min_redraw_interval
260266
self.max_render_postpone_time = max_render_postpone_time
261267
self.refresh_interval = refresh_interval
268+
self.terminal_size_polling_interval = terminal_size_polling_interval
262269

263270
# Events.
264271
self.on_invalidate = Event(self, on_invalidate)
@@ -696,6 +703,8 @@ def flush_input() -> None:
696703
with self.input.raw_mode(), self.input.attach(
697704
read_from_input
698705
), attach_winch_signal_handler(self._on_resize):
706+
self.create_background_task(self._poll_output_size())
707+
699708
# Draw UI.
700709
self._request_absolute_cursor_position()
701710
self._redraw()
@@ -873,6 +882,28 @@ async def cancel_and_wait_for_background_tasks(self) -> None:
873882
except CancelledError:
874883
pass
875884

885+
async def _poll_output_size(self) -> None:
886+
"""
887+
Coroutine for polling the terminal dimensions.
888+
889+
Useful for situations where `attach_winch_signal_handler` is not sufficient:
890+
- If we are not running in the main thread.
891+
- On Windows.
892+
"""
893+
size: Optional[Size] = None
894+
interval = self.terminal_size_polling_interval
895+
896+
if interval is None:
897+
return
898+
899+
while True:
900+
await asyncio.sleep(interval)
901+
new_size = self.output.get_size()
902+
903+
if size is not None and new_size != size:
904+
self._on_resize()
905+
size = new_size
906+
876907
def cpr_not_supported_callback(self) -> None:
877908
"""
878909
Called when we don't receive the cursor position response in time.

0 commit comments

Comments
 (0)