Skip to main content

rich_interact/
component.rs

1//! The component contract: one event in, one view out.
2
3use std::time::Duration;
4
5use rich::{Console, Renderable, Segment};
6
7use crate::event::Event;
8use crate::keymap::Keymap;
9use crate::policy::{LineIo, NotInteractive};
10
11/// What a component wants after handling an event.
12#[derive(Debug)]
13pub enum Flow<T> {
14    /// Keep going. The view is rendered again, and repainted only if it
15    /// changed.
16    Continue,
17    /// Finished with a value.
18    Done(T),
19    /// The user backed out (Escape, `q`): no value.
20    Cancel,
21    /// The event was not for this component: it is left to the container
22    /// the component is in, which may use it itself (a focus key, a
23    /// binding of its own) or pass it on to its own container (0.0.14).
24    /// The event loop, at the top, treats it as [`Continue`](Flow::Continue).
25    Ignored,
26    /// Give the terminal to `command` (`$EDITOR file`, a pager) until it
27    /// exits, then take it back and repaint; the component then gets
28    /// [`Event::Returned`] with the exit code (#489).
29    Handoff(std::process::Command),
30}
31
32/// A rendered view: lines of segments, and where the terminal cursor goes
33/// (a text caret), or `None` to hide it.
34#[derive(Clone, Debug, Default)]
35pub struct View {
36    pub lines: Vec<Vec<Segment>>,
37    /// (row, column) within the view.
38    pub cursor: Option<(usize, usize)>,
39}
40
41impl View {
42    pub fn new(lines: Vec<Vec<Segment>>) -> View {
43        View {
44            lines,
45            cursor: None,
46        }
47    }
48
49    #[must_use]
50    pub fn with_cursor(mut self, row: usize, column: usize) -> View {
51        self.cursor = Some((row, column));
52        self
53    }
54
55    /// Add `other` below this view. Its cursor, if this view has none, moves
56    /// down with it.
57    pub fn push(&mut self, other: View) {
58        if self.cursor.is_none() {
59            self.cursor = other
60                .cursor
61                .map(|(row, column)| (row + self.lines.len(), column));
62        }
63        self.lines.extend(other.lines);
64    }
65}
66
67/// What a component renders for: the console to render with, and the space
68/// it may use.
69pub struct Context<'a> {
70    pub console: &'a Console,
71    /// Columns available.
72    pub width: usize,
73    /// Rows available: the terminal's height, or the inline height the
74    /// caller set. A view taller than this is cut off at the bottom.
75    pub height: usize,
76}
77
78impl Context<'_> {
79    /// Render `renderable` at the context's width into lines.
80    pub fn lines(&self, renderable: &dyn Renderable) -> Vec<Vec<Segment>> {
81        self.lines_at(renderable, self.width)
82    }
83
84    /// Render `renderable` at `width` columns into lines.
85    pub fn lines_at(&self, renderable: &dyn Renderable, width: usize) -> Vec<Vec<Segment>> {
86        let options = self.console.options().update_width(width.max(1));
87        self.console.render_lines(renderable, &options, false)
88    }
89
90    /// Render console markup into lines.
91    pub fn markup(&self, markup: &str) -> Vec<Vec<Segment>> {
92        let text = rich::Text::from_markup(markup).unwrap_or_else(|_| rich::Text::new(markup));
93        self.lines(&text)
94    }
95}
96
97/// An interactive component: a state machine that handles one event at a
98/// time and renders its state. The same component runs under
99/// [`run`](crate::run) and in an [`EventLoop`](crate::EventLoop), and in the
100/// [headless](crate::headless) driver for tests.
101pub trait Component {
102    type Output;
103
104    /// Handle one event. Ctrl+C never arrives here: the event loop ends the
105    /// component with [`Outcome::Interrupted`](crate::Outcome::Interrupted).
106    fn handle(&mut self, event: &Event, context: &Context<'_>) -> Flow<Self::Output>;
107
108    /// Called once, before the component is first painted, with the
109    /// terminal's size: for work that needs it, such as rendering content
110    /// at the width. Returning `Done` or `Cancel` finishes the component
111    /// without waiting for an event. The default does nothing.
112    fn start(&mut self, context: &Context<'_>) -> Flow<Self::Output> {
113        let _ = context;
114        Flow::Continue
115    }
116
117    /// Render the current state.
118    fn render(&self, context: &Context<'_>) -> View;
119
120    /// How often the component wants [`Event::Tick`], for a spinner or a
121    /// countdown. `None` (the default): never.
122    fn tick(&self) -> Option<Duration> {
123        None
124    }
125
126    /// Whether the component wants mouse events (#476): clicks, drags and
127    /// the wheel. Off by default, since reporting the mouse takes text
128    /// selection away from the terminal; a component turns it on when its
129    /// caller opts in, and [`run`](crate::run) then enables mouse reporting
130    /// for the session.
131    fn mouse(&self) -> bool {
132        false
133    }
134
135    /// The bindings this component has right now, for a help overlay, a
136    /// shortcut list or status-bar hints to read (see
137    /// [`keymap`](crate::keymap)). A container's include its focused
138    /// child's, before its own. The default declares none.
139    fn keymap(&self) -> Keymap {
140        Keymap::default()
141    }
142
143    /// Whether the component takes focus in a container: Tab stops on it
144    /// and keys go to it. The default: yes. A label, or a component that
145    /// has finished, says no.
146    fn focusable(&self) -> bool {
147        true
148    }
149
150    /// Move focus to the next (`forward`) or previous focus stop *inside*
151    /// this component, for Tab and Shift+Tab. `false` when there is none
152    /// after the current one, so the container moves on to its next
153    /// child. The default, for a component that is one stop: `false`.
154    fn focus_step(&mut self, forward: bool) -> bool {
155        let _ = forward;
156        false
157    }
158
159    /// Take focus from outside: at the first stop inside when `forward`,
160    /// at the last otherwise. `false` when nothing in it can take focus.
161    /// The default: whether the component is [`focusable`](Self::focusable).
162    fn focus_enter(&mut self, forward: bool) -> bool {
163        let _ = forward;
164        self.focusable()
165    }
166
167    /// The value to return when the terminal is not interactive and the
168    /// policy asks for defaults. `None`: there is none.
169    fn default_value(&self) -> Option<Self::Output> {
170        None
171    }
172
173    /// Ask for the value line by line, for when the terminal is not
174    /// interactive and the policy asks for a prompt. The default says the
175    /// component has no line-based form.
176    ///
177    /// `Ok(None)` means the user backed out ([`Outcome::Cancelled`](crate::Outcome::Cancelled)).
178    /// When input ends before an answer, the built-in components answer
179    /// with their default, and without one return [`NotInteractive::Ended`],
180    /// which [`degrade`](crate::degrade) reports as
181    /// [`NotInteractive::NoDefault`]: end of input is no answer, not a
182    /// refusal.
183    fn prompt(&mut self, io: &mut dyn LineIo) -> Result<Option<Self::Output>, NotInteractive> {
184        let _ = io;
185        Err(NotInteractive::NoPrompt)
186    }
187}