Phosphor::App(S, M)
Adds theme access to App. Theme itself lives in theme.cr.
Widget#sync calls app.theme to resolve semantic color names into
concrete Style values for MockWidget::Props. Exposed as a settable
property so tests (and apps) can inject a custom theme.
Constants
Constructors
Instance methods
Registers port under screen, starts its fiber, and returns.
The port is appended to the registry entry for screen, creating the
entry on the first call for that screen. When screen unmounts, the
framework calls stop_ports_for(screen) to stop and deregister all
ports registered here — screens do not need to manage this themselves.
Registers port scoped to the currently mounting screen.
Convenience overload for Screen#on_mount where the screen reference is
implicit — the framework sets the active screen before calling on_mount
and clears it after. Raises if called outside of that lifecycle; use
add_port(port, screen) from any other context.
The terminal's color rendering capability.
Stubbed as TrueColor until Phase 15 adds $COLORTERM / $TERM / $NO_COLOR
detection. buf.set_string uses this to downsample colors before writing ANSI.
Whether the terminal uses a dark background.
Stubbed as true until Phase 15 adds OSC 11 detection. Widgets and themes
use this to choose appropriate Style variants in Widget#sync.
The shared channel every Port (including ones registered via
#add_port) writes events onto. Exposed read-only so specs driving
a Port directly (e.g. FakeTickPort#tick!) can confirm what does
or doesn't arrive, without needing their own separate channel.
Erases the active Inline block entirely, in response to
Cmd::InlineDismiss — unlike normal exit, no trace of the block is
left in scrollback. Only clears the block; the dispatch loop that
calls this still sets the quit flag itself afterward, since a plain
method can't reach #run's local quit variable — that keeps the
existing quit path (and its ensure-guaranteed Terminal#exit)
responsible for actually stopping the loop, per Cmd::InlineDismiss's
contract of not calling Terminal#exit directly.
Inline mode only — raises RuntimeError in Fullscreen, same as
#print.
See DESIGN.md § "Inline block transitions".
Changes the active Inline block's height, in response to
Cmd::InlineResize. Clears the old block, re-reserves the terminal
rows at the new height, and forces a full redraw. Content is not
preserved across the resize.
Inline mode only — raises RuntimeError in Fullscreen, same as
#print.
See DESIGN.md § "Inline block transitions".
Returns the ports currently registered for screen, or an empty
array if it has none (including after #stop_ports_for removed its
entry). Read-only counterpart to #add_port/#stop_ports_for, for
callers and specs that need to confirm registration state rather
than change it.
Writes line directly above the active Inline block, into
scrollback, without disturbing the block itself — the phosphor
equivalent of Bubbletea's tea.Println. Use it for a persistent trail
of completed steps: ==> Downloading... lines above a progress bar,
[OK] lines as steps finish.
Inline mode only — raises RuntimeError in Fullscreen, where the
alt screen has no notion of "above the block" to write into. Use Log
for diagnostic output there instead.
See DESIGN.md § "App#print — writing completed steps to scrollback".
Returns the running session's Renderer — used by
Screen#transition_widget to drive View#transition_widget's
renderer.invalidate call once playback completes.
Raises if called before #run, the same @renderer || raise
pattern #push_screen/#pop_screen/#replace_screen/#print
already use for this ivar — there's no Renderer to return outside
a running session.
The Router::Definition passed to #run, or nil if the app didn't
pass one. HelpScreen reads this to render app.router.generate_help
— see DESIGN.md § "Router: your routes.rb". Settable (like #theme)
so specs can inject one directly without going through #run, which
isn't safely callable outside a real terminal session.
The Router::Definition passed to #run, or nil if the app didn't
pass one. HelpScreen reads this to render app.router.generate_help
— see DESIGN.md § "Router: your routes.rb". Settable (like #theme)
so specs can inject one directly without going through #run, which
isn't safely callable outside a real terminal session.
Boots the application and runs the main event loop until the process exits.
Creates a fresh Terminal, Renderer, event channel, and the initial
screen, then enters a loop that drains events, dispatches messages, handles
state transitions, fires async Cmds, and renders each frame.
terminal.enter is called before the loop with the shared event channel so
SIGWINCH pushes a ResizeEvent into the loop automatically. terminal.exit
is guaranteed by an ensure block so raw mode and the alternate screen are
always restored.
initial_state is the first S value passed to initial_screen's
on_mount. initial_screen is the Screen(S, M) subclass the app starts on.
mode selects RenderMode::Fullscreen (the alternate screen buffer,
the default) or RenderMode::Inline (a fixed-height block in normal
terminal flow, below the shell prompt). height is required when
mode is Inline; unused otherwise. Pass Terminal::AUTO_HEIGHT
instead of an Int32 to grow the block to fit the initial screen's
mounted widgets' declared content (MockWidget#preferred_height)
instead of using a fixed row count — re-measured on every
ResizeEvent, since some widgets (e.g. ParagraphMock) wrap
differently at a new width. Falls back to Terminal::DEFAULT_AUTO_HEIGHT
when no mounted widget declares a preference.
theme is the Theme widgets resolve semantic color names against via
app.theme — see Widget#sync. Defaults to Theme.new.
Set mouse to true to enable mouse tracking (clicks, drags, wheel
scroll), delivered as MouseEvents. Off by default.
log_path, log_max_size, and log_keep configure Logger.setup:
where the JSON-lines log file is written (default
${XDG_STATE_HOME:-$HOME/.local/state}/phosphor/<program_name>.log),
its size-based rotation threshold (default 10 MB), and how many
rotated files to keep (default 5).
Set debug to true to enable the debug overlay (the app's own
state, View focus/subscription info), toggled on and off with debug_hotkey
(default Key::F12). debug defaults to false so a production app
can never accidentally expose internal state just because a user
happens to press F12 — the hotkey does nothing unless explicitly
opted into.
debug_scroll_down_hotkey (default Key::F11) and
debug_scroll_up_hotkey (default Key::ShiftF11) scroll the overlay's
own content without moving focus away from the active screen — the
main app keeps receiving every other key normally while the overlay is
open. Like debug_hotkey, both do nothing unless debug is true;
unlike debug_hotkey, they're swallowed (never reach the screen)
whenever debug is true, even while the overlay itself is closed —
see #dispatch_event. Scroll position resets to the top each time the
overlay closes, so it never reopens mid-scroll.
Pass router (the Router::Definition returned by Router.define)
to make it available as app.router — the built-in HelpScreen reads
it to render a keybinding reference. Omit it (nil) if the app
doesn't use Router; HelpScreen falls back to empty content rather
than raising.
Set stats to true to have the debug overlay display a performance
section — FPS plus Renderer#stats (cells changed, bytes written,
average draw time) — above the State:/View: info that's always
there whenever the overlay is visible. With debug alone (no
stats), that whole section is omitted, not just the Renderer#stats
part of it. Only takes effect together with debug and the debug
hotkey, same as the rest of the overlay. Renderer always collects
these numbers regardless of stats; this only controls whether
they're shown.
max_fps caps how often the main loop actually draws — Renderer#draw
skips (returns without rendering) any call arriving less than
1.second / max_fps after the previous completed draw. Defaults to
60. This bounds wasted work when events arrive faster than the
terminal can usefully redraw (e.g. a burst of resolved Cmds, or a
widget with a very short animation_interval); it does not slow down
an app that already renders less often than the cap.
transition is the Transition(S, M) used to animate between screens,
or nil (the default) for the existing instant swap, via
NoTransition. Not yet consumed by Cmd::Push/Cmd::Pop/
Cmd::Replace — see @transition's own doc comment.
backend is the Backend the session's Terminal delegates to —
nil (the default) leaves Terminal.new to build its own
AnsiBackend, i.e. a real terminal, with no change in behavior.
Pass a TestBackend here to drive #run in a spec without a real
terminal:
# Default — real terminal, no change required at existing call sites
App.new.run(state, MyScreen)
# Test — inject TestBackend
backend = TestBackend(MyState, MyMsg).new
App.new.run(state, MyScreen, backend: backend)
Whether App.run(stats: true) was passed — gates only whether the
debug overlay displays Renderer#stats, not whether Renderer
collects it (that's unconditional; see Renderer#draw's doc comment).
Stops every port registered for screen and removes it from the registry.
Called by the screen navigation machinery (Phase 19) when a screen pauses or unmounts. Each port's fiber exits cleanly on its next iteration. Does nothing if screen has no registered ports.
Returns the running session's Terminal — used by
Screen#transition_widget to drive View#transition_widget's
renderer.draw_frame calls, which need a Terminal to flush each
transition step to as it plays.
Raises if called before #run, same @terminal || raise pattern
#renderer above already uses for its own ivar.