1use std::collections::HashSet;
4
5use crate::{
6 core::{
7 crossterm::{
8 self,
9 event::Event,
10 style::{Attribute, Attributes, Color, ContentStyle},
11 },
12 render::{Renderer, SharedRenderer},
13 PaneFactory,
14 },
15 preset::Evaluator,
16 suggest::Suggest,
17 validate::{ErrorMessageGenerator, Validator, ValidatorManager},
18 widgets::{
19 listbox::{self, Listbox},
20 text::{self, Text},
21 text_editor::{self, History},
22 },
23 Signal,
24};
25
26pub mod evaluate;
27
28#[derive(PartialEq, Eq, PartialOrd, Ord)]
30pub enum Index {
31 Title = 0,
32 Readline = 1,
33 Suggestion = 2,
34 ErrorMessage = 3,
35}
36
37pub enum Focus {
40 Readline,
41 Suggestion,
42}
43
44pub struct Readline {
49 pub renderer: Option<SharedRenderer<Index>>,
51 pub evaluator: Evaluator<Self>,
53 pub focus: Focus,
55 pub title: text::State,
57 pub readline: text_editor::State,
59 pub suggest: Option<Suggest>,
61 pub suggestions: listbox::State,
63 pub validator: Option<ValidatorManager<str>>,
65 pub error_message: text::State,
67}
68
69impl Default for Readline {
70 fn default() -> Self {
71 Self {
72 renderer: None,
73 evaluator: |event, ctx| Box::pin(evaluate::default(event, ctx)),
74 focus: Focus::Readline,
75 title: text::State {
76 style: ContentStyle {
77 attributes: Attributes::from(Attribute::Bold),
78 ..Default::default()
79 },
80 ..Default::default()
81 },
82 readline: text_editor::State {
83 texteditor: Default::default(),
84 history: Default::default(),
85 prefix: String::from("❯❯ "),
86 mask: Default::default(),
87 prefix_style: ContentStyle {
88 foreground_color: Some(Color::DarkGreen),
89 ..Default::default()
90 },
91 active_char_style: ContentStyle {
92 background_color: Some(Color::DarkCyan),
93 ..Default::default()
94 },
95 inactive_char_style: ContentStyle::default(),
96 edit_mode: Default::default(),
97 word_break_chars: HashSet::from([' ']),
98 lines: Default::default(),
99 },
100 suggest: Default::default(),
101 suggestions: listbox::State {
102 listbox: Listbox::from_displayable(Vec::<String>::new()),
103 cursor: String::from("❯ "),
104 active_item_style: Some(ContentStyle {
105 foreground_color: Some(Color::DarkGrey),
106 background_color: Some(Color::DarkYellow),
107 ..Default::default()
108 }),
109 inactive_item_style: Some(ContentStyle {
110 foreground_color: Some(Color::DarkGrey),
111 ..Default::default()
112 }),
113 lines: Some(3),
114 },
115 validator: Default::default(),
116 error_message: text::State {
117 text: Default::default(),
118 style: ContentStyle {
119 foreground_color: Some(Color::DarkRed),
120 attributes: Attributes::from(Attribute::Bold),
121 ..Default::default()
122 },
123 lines: None,
124 },
125 }
126 }
127}
128
129#[async_trait::async_trait]
130impl crate::Prompt for Readline {
131 async fn initialize(&mut self) -> anyhow::Result<()> {
132 let size = crossterm::terminal::size()?;
133 self.renderer = Some(SharedRenderer::new(
134 Renderer::try_new_with_panes(
135 [
136 (Index::Title, self.title.create_pane(size.0, size.1)),
137 (Index::Readline, self.readline.create_pane(size.0, size.1)),
138 (
139 Index::Suggestion,
140 self.suggestions.create_pane(size.0, size.1),
141 ),
142 (
143 Index::ErrorMessage,
144 self.error_message.create_pane(size.0, size.1),
145 ),
146 ],
147 true,
148 )
149 .await?,
150 ));
151 Ok(())
152 }
153
154 async fn evaluate(&mut self, event: &Event) -> anyhow::Result<Signal> {
155 let ret = (self.evaluator)(event, self).await;
156 let size = crossterm::terminal::size()?;
157 self.render(size.0, size.1).await?;
158 ret
159 }
160
161 type Return = String;
162
163 fn finalize(&mut self) -> anyhow::Result<Self::Return> {
164 let ret = self.readline.texteditor.text_without_cursor().to_string();
165
166 self.readline.texteditor.erase_all();
168
169 Ok(ret)
170 }
171}
172
173impl Readline {
174 pub fn title<T: AsRef<str>>(mut self, text: T) -> Self {
176 self.title.text = Text::from(text);
177 self
178 }
179
180 pub fn title_style(mut self, style: ContentStyle) -> Self {
182 self.title.style = style;
183 self
184 }
185
186 pub fn enable_suggest(mut self, suggest: Suggest) -> Self {
188 self.suggest = Some(suggest);
189 self
190 }
191
192 pub fn enable_history(mut self) -> Self {
194 self.readline.history = Some(History::default());
195 self
196 }
197
198 pub fn prefix<T: AsRef<str>>(mut self, prefix: T) -> Self {
200 self.readline.prefix = prefix.as_ref().to_string();
201 self
202 }
203
204 pub fn mask(mut self, mask: char) -> Self {
206 self.readline.mask = Some(mask);
207 self
208 }
209
210 pub fn prefix_style(mut self, style: ContentStyle) -> Self {
212 self.readline.prefix_style = style;
213 self
214 }
215
216 pub fn active_char_style(mut self, style: ContentStyle) -> Self {
218 self.readline.active_char_style = style;
219 self
220 }
221
222 pub fn inactive_char_style(mut self, style: ContentStyle) -> Self {
224 self.readline.inactive_char_style = style;
225 self
226 }
227
228 pub fn edit_mode(mut self, mode: text_editor::Mode) -> Self {
230 self.readline.edit_mode = mode;
231 self
232 }
233
234 pub fn word_break_chars(mut self, characters: HashSet<char>) -> Self {
236 self.readline.word_break_chars = characters;
237 self
238 }
239
240 pub fn text_editor_lines(mut self, lines: usize) -> Self {
242 self.readline.lines = Some(lines);
243 self
244 }
245
246 pub fn evaluator(mut self, evaluator: Evaluator<Self>) -> Self {
248 self.evaluator = evaluator;
249 self
250 }
251
252 pub fn validator(
254 mut self,
255 validator: Validator<str>,
256 error_message_generator: ErrorMessageGenerator<str>,
257 ) -> Self {
258 self.validator = Some(ValidatorManager::new(validator, error_message_generator));
259 self
260 }
261
262 async fn render(&mut self, width: u16, height: u16) -> anyhow::Result<()> {
264 match self.renderer.as_ref() {
265 Some(renderer) => {
266 renderer
267 .update([
268 (Index::Title, self.title.create_pane(width, height)),
269 (Index::Readline, self.readline.create_pane(width, height)),
270 (
271 Index::Suggestion,
272 self.suggestions.create_pane(width, height),
273 ),
274 (
275 Index::ErrorMessage,
276 self.error_message.create_pane(width, height),
277 ),
278 ])
279 .render()
280 .await
281 }
282 None => Err(anyhow::anyhow!("Renderer not initialized")),
283 }
284 }
285}