A simple TUI Library written in Rust
1use crate::component::{Component, RunError};
2use crate::input::RawMode;
3
4/// A sequential composition of interactive components.
5///
6/// Each step runs one `Component` to completion, then passes its result
7/// to a continuation that produces the next `Flow`. The whole thing resolves
8/// to a single `R` when complete.
9///
10/// # Example
11///
12/// ```rust,ignore
13/// use sly::flow::Flow;
14/// use sly::widgets::text_input::{text_input, TextInput};
15/// use sly::widgets::confirm::confirm_str;
16///
17/// let flow: Flow<Option<String>> = Flow::step(
18/// text_input(TextInput::new().prompt("Name: ")),
19/// |name| {
20/// Flow::step(
21/// confirm_str(&format!("Save as '{}'?", name), true),
22/// move |ok| {
23/// if ok { Flow::done(Some(name)) } else { Flow::done(None) }
24/// },
25/// )
26/// },
27/// );
28///
29/// let result = flow.run(&raw_mode).unwrap();
30/// ```
31pub struct Flow<R: 'static> {
32 inner: Box<dyn FnOnce(&RawMode) -> Result<R, RunError>>,
33}
34
35impl<R: 'static> Flow<R> {
36 /// Run one component step, then pass its result to a continuation.
37 pub fn step<S, T>(component: Component<S, T>, then: impl FnOnce(T) -> Flow<R> + 'static) -> Self
38 where
39 S: 'static,
40 T: 'static,
41 {
42 Flow {
43 inner: Box::new(move |mode| {
44 let result = component.run(mode)?;
45 (then(result)).run(mode)
46 }),
47 }
48 }
49
50 /// A flow that immediately succeeds with `value`.
51 pub fn done(value: R) -> Self {
52 Flow {
53 inner: Box::new(move |_mode| Ok(value)),
54 }
55 }
56
57 /// A flow that immediately fails with `Cancelled`.
58 pub fn cancel() -> Self {
59 Flow {
60 inner: Box::new(|_mode| Err(RunError::Cancelled)),
61 }
62 }
63
64 /// Map the result value.
65 pub fn map<R2: 'static>(self, f: impl FnOnce(R) -> R2 + 'static) -> Flow<R2> {
66 Flow {
67 inner: Box::new(move |mode| self.run(mode).map(f)),
68 }
69 }
70
71 /// Run the entire flow to completion.
72 pub fn run(self, mode: &RawMode) -> Result<R, RunError> {
73 (self.inner)(mode)
74 }
75}
76
77// ---------------------------------------------------------------------------
78// Helper shorthands
79// ---------------------------------------------------------------------------
80
81use crate::widgets::confirm::confirm_str;
82use crate::widgets::selector::{Selector, SelectorItem, selector};
83use crate::widgets::text_input::{TextInput, text_input};
84
85/// Run a text-input prompt as a single flow step.
86pub fn ask(prompt: &str, input: TextInput) -> Flow<String> {
87 let prompt = prompt.to_owned();
88 let ti = input.prompt(&prompt);
89 Flow::step(text_input(ti), Flow::done)
90}
91
92/// Run a selector as a single flow step.
93pub fn choose<T: Clone + 'static>(items: Vec<SelectorItem<T>>) -> Flow<T> {
94 let sel = Selector::new(items);
95 Flow::step(selector(sel), Flow::done)
96}
97
98/// Run a yes/no confirmation as a single flow step.
99pub fn confirm(message: &str, default: bool) -> Flow<bool> {
100 Flow::step(confirm_str(message, default), Flow::done)
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 // We test Flow by constructing flows that don't touch a real terminal.
108 // We use `Flow::done` and `Flow::cancel` directly, and test that `map`
109 // and chaining work correctly via the inner closure without running
110 // actual IO components.
111
112 /// A helper that builds a Flow from a pre-computed result.
113 #[allow(dead_code)]
114 fn immediate<R: 'static>(r: Result<R, RunError>) -> Flow<R> {
115 // We can't easily inject a raw mode, but we can test the logic of
116 // done/cancel/map by relying on the fact that `done` and `cancel`
117 // ignore the mode argument.
118 match r {
119 Ok(v) => Flow::done(v),
120 Err(_) => Flow::cancel(),
121 }
122 }
123
124 #[test]
125 fn flow_done_produces_value() {
126 // We can't call .run() without a RawMode, but we can verify the
127 // structure: Flow::done wraps a closure that returns Ok(value).
128 // Test indirectly via map.
129 let f = Flow::done(42usize).map(|n| n * 2);
130 // f is a Flow<usize>. We verify it's constructible.
131 let _ = f; // just ensure it compiles without panicking
132 }
133
134 #[test]
135 fn flow_map_transforms_result() {
136 // Construct two immediate flows and chain them.
137 let _f: Flow<String> = Flow::done(10usize).map(|n| format!("val={}", n));
138 }
139
140 #[test]
141 fn flow_step_chains() {
142 // Build a flow that chains two done steps.
143 let _f: Flow<String> = Flow::step(
144 {
145 use crate::component::Component;
146 // A component whose run() we can't call in a unit test (needs RawMode),
147 // so we just verify the type chain compiles.
148 Component::new(
149 (),
150 |_| Block::text(vec![]),
151 |_, _key| crate::component::Update::Finish("hello".to_string()),
152 )
153 },
154 |s: String| Flow::done(format!("got: {}", s)),
155 );
156 }
157
158 #[test]
159 fn flow_cancel_type_checks() {
160 let _f: Flow<u32> = Flow::cancel();
161 }
162
163 use crate::block::Block;
164}