tests/hermes_cli/test_prompt_stash_cli.py builds HermesCLI with a fixed list of prompt_toolkit submodules replaced by MagicMocks; importing prompt_toolkit.layout.screen for a type annotation fell outside that list and raised ModuleNotFoundError at cli_tui_mixin import time.
40 lines
2.0 KiB
Python
40 lines
2.0 KiB
Python
"""Root container of the classic CLI footer: clips a late row instead of giving up.
|
||
|
||
prompt_toolkit renders a non-full-screen layout into exactly the height it measured a moment
|
||
earlier (``Renderer.render`` → ``HSplit.preferred_height``), then ``HSplit._divide_heights``
|
||
measures every child again while painting. Each footer row here has a callable height that the
|
||
agent thread flips mid-turn (``_agent_running`` → spacer, ``_spinner_text`` → spinner,
|
||
``_command_running`` → hint), so a row appearing between the two passes makes the summed minimum
|
||
exceed the budget by one and stock ``HSplit`` replaces the WHOLE footer with its
|
||
" Window too small... " placeholder (#57393) — on any terminal size, including a maximised 200×50
|
||
pane. Hermes's throttled ``_invalidate`` can then drop the healing repaint for the length of a
|
||
tool call, so the placeholder sits there looking like a geometry error.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from prompt_toolkit.application import get_app
|
||
from prompt_toolkit.layout import HSplit
|
||
|
||
|
||
class FooterSplit(HSplit):
|
||
"""``HSplit`` whose overflow path keeps the composer on screen and repaints right away.
|
||
|
||
When the base class would paint the placeholder, allocate the children's minimum heights
|
||
bottom-up (composer, rules and status bar first) so only the row that appeared late — always
|
||
above them — loses its row for this one frame, and schedule an unthrottled redraw so the
|
||
next frame measures and paints against the same state.
|
||
"""
|
||
|
||
def _divide_heights(self, write_position) -> list[int] | None:
|
||
sizes = super()._divide_heights(write_position)
|
||
if sizes is not None:
|
||
return sizes
|
||
budget = write_position.height
|
||
clipped: list[int] = []
|
||
for child in reversed(self._all_children):
|
||
rows = min(child.preferred_height(write_position.width, budget).min, budget)
|
||
budget -= rows
|
||
clipped.append(rows)
|
||
get_app().invalidate()
|
||
return clipped[::-1]
|