1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! Definitions of common behavior shared amongst all different prompt types.
use crate::{error::InquireResult, input::InputActionResult, ui::CommonBackend, InquireError};
use super::action::{Action, InnerAction};
/// 描述组件处理完内部动作后,希望外层 Run Loop 怎么做
pub enum ActionState {
/// 触发重新渲染或清屏幕
Render(ActionResult),
/// 提交当前数据
RequestSubmit,
/// 取消操作
RequestCancel,
}
impl From<ActionResult> for ActionState {
fn from(value: ActionResult) -> Self {
ActionState::Render(value)
}
}
/// Represents the result of an action on the prompt.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ActionResult {
/// The action resulted in a state change that requires the prompt to be
/// re-rendered.
NeedsRedraw,
/// The action either didn't result in a state change or the state
/// change does not require a redraw.
Clean,
}
impl ActionResult {
pub fn merge(self, other: Self) -> Self {
match (self, other) {
(Self::NeedsRedraw, _) | (_, Self::NeedsRedraw) => Self::NeedsRedraw,
(Self::Clean, Self::Clean) => Self::Clean,
}
}
/// Returns whether the action requires a redraw.
pub fn needs_redraw(&self) -> bool {
matches!(self, Self::NeedsRedraw)
}
}
impl From<InputActionResult> for ActionResult {
fn from(value: InputActionResult) -> Self {
if value.needs_redraw() {
Self::NeedsRedraw
} else {
Self::Clean
}
}
}
/// Shared behavior among all different prompt types.
pub trait Prompt<Backend>
where
Backend: CommonBackend,
Self: Sized,
{
type Config;
type InnerAction: InnerAction<Config = Self::Config>;
type Output;
/// Prompt header rendered to the user.
fn message(&self) -> &str;
/// Returns the underlying settings of the prompt, used, among other
/// goals, to parse a key event into a prompt action.
///
/// For example, a prompt might be configured to have vim mode enabled
/// or disabled, which affects how certain key events are parsed into
/// actions to the prompt.
fn config(&self) -> &Self::Config;
/// Hook called when a prompt is finished. Returns a string
/// to be rendered to the user as the final submission to the prompt.
///
/// # Arguments
///
/// * `answer` - Answer returned by the prompt.
fn format_answer(&self, answer: &Self::Output) -> String;
/// Hook called when a prompt is first started, before the first
/// draw happens.
fn setup(&mut self) -> InquireResult<()> {
Ok(())
}
/// Hook called when an input to cancel the prompt is triggered.
///
/// Returns whether the prompt can be terminated.
fn pre_cancel(&mut self) -> InquireResult<bool> {
Ok(true)
}
/// Hook called when the user submits the answer to the prompt.
///
/// On success, it should return `Some(ReturnType)` when the user
/// submission is valid and the prompt can graciously return.
///
/// If the user submission is invalid or should be rejected for some reason,
/// this method should return `Ok(None)`.
///
/// On `Err(*)`, the prompt is teared down.
fn submit(&mut self) -> InquireResult<Option<Self::Output>>;
/// Entrypoint for any business logic for the prompt. Returns the result
/// of the action. If the result is `Clean`, the prompt will
/// not be re-rendered.
///
/// On the usual path, users' key presses are parsed into prompt actions,
/// which are then submitted to this method to be handled.
///
/// On testing scenarios, developers might provide a stream of actions
/// to the prompt, which will then be submitted to this method just the same.
fn handle(&mut self, action: Self::InnerAction) -> InquireResult<ActionState>;
/// Hook called for the rendering of the prompt UI.
///
/// The implementation should **not** call neither `frame_setup` or
/// `frame_finish` methods of the underlying backend, as this is handled
/// by the top-level prompt method.
fn render(&self, backend: &mut Backend) -> InquireResult<()>;
/// Top-level implementation of a prompt's flow.
///
/// This should not be reimplemented by types that implement this trait,
/// unless the situation really warrants it.
fn prompt(mut self, backend: &mut Backend) -> InquireResult<Self::Output> {
self.setup()?;
let mut last_handle = ActionResult::NeedsRedraw;
let final_answer = loop {
if last_handle.needs_redraw() {
backend.frame_setup()?;
self.render(backend)?;
backend.frame_finish(false)?;
last_handle = ActionResult::Clean;
}
let key = backend.read_key()?;
let action = Action::from_key(key, self.config());
if let Some(action) = action {
last_handle = match action {
Action::Submit => {
if let Some(answer) = self.submit()? {
break answer;
}
ActionResult::NeedsRedraw
}
Action::Cancel => {
let pre_cancel_result = self.pre_cancel()?;
if pre_cancel_result {
backend.frame_setup()?;
backend.render_canceled_prompt(self.message())?;
backend.frame_finish(true)?;
return Err(InquireError::OperationCanceled);
}
ActionResult::NeedsRedraw
}
Action::Interrupt => return Err(InquireError::OperationInterrupted),
Action::Inner(inner_action) => match self.handle(inner_action)? {
ActionState::Render(action_result) => action_result,
ActionState::RequestSubmit => {
if let Some(answer) = self.submit()? {
break answer;
}
ActionResult::NeedsRedraw
}
ActionState::RequestCancel => {
let pre_cancel_result = self.pre_cancel()?;
if pre_cancel_result {
backend.frame_setup()?;
backend.render_canceled_prompt(self.message())?;
backend.frame_finish(true)?;
return Err(InquireError::OperationCanceled);
}
ActionResult::NeedsRedraw
}
},
};
}
};
let formatted = self.format_answer(&final_answer);
backend.frame_setup()?;
backend.render_prompt_with_answer(self.message(), &formatted)?;
backend.frame_finish(true)?;
Ok(final_answer)
}
}