xacli_components/components/
input.rs1use std::io::{stdout, Write};
2
3use crossterm::{
4 cursor,
5 event::{self, Event, KeyCode, KeyModifiers},
6 queue,
7 style::Print,
8 terminal::{self, ClearType},
9};
10
11use crate::{ComponentError, Result};
12
13pub struct Input {
14 prompt: String,
15 default: Option<String>,
16}
17
18impl Input {
19 pub fn new(prompt: impl Into<String>) -> Self {
20 Self {
21 prompt: prompt.into(),
22 default: None,
23 }
24 }
25
26 pub fn default(mut self, default: impl Into<String>) -> Self {
27 self.default = Some(default.into());
28 self
29 }
30
31 pub fn run(self) -> Result<String> {
32 let mut stdout = stdout();
33 terminal::enable_raw_mode()?;
34
35 let result = self.run_inner(&mut stdout);
36
37 terminal::disable_raw_mode()?;
38 println!(); result
41 }
42
43 fn run_inner(&self, stdout: &mut impl Write) -> Result<String> {
44 let mut input = self.default.clone().unwrap_or_default();
45 let mut cursor_pos = input.len();
46
47 self.render(stdout, &input, cursor_pos)?;
49
50 loop {
51 if let Event::Key(key) = event::read()? {
52 match key.code {
53 KeyCode::Enter => {
54 return Ok(input);
55 }
56 KeyCode::Esc => {
57 return Err(ComponentError::Interrupted);
58 }
59 KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
60 return Err(ComponentError::Interrupted);
61 }
62 KeyCode::Char(c) => {
63 input.insert(cursor_pos, c);
64 cursor_pos += 1;
65 }
66 KeyCode::Backspace => {
67 if cursor_pos > 0 {
68 cursor_pos -= 1;
69 input.remove(cursor_pos);
70 }
71 }
72 KeyCode::Delete => {
73 if cursor_pos < input.len() {
74 input.remove(cursor_pos);
75 }
76 }
77 KeyCode::Left => {
78 cursor_pos = cursor_pos.saturating_sub(1);
79 }
80 KeyCode::Right => {
81 if cursor_pos < input.len() {
82 cursor_pos += 1;
83 }
84 }
85 KeyCode::Home => {
86 cursor_pos = 0;
87 }
88 KeyCode::End => {
89 cursor_pos = input.len();
90 }
91 _ => {}
92 }
93
94 self.render(stdout, &input, cursor_pos)?;
95 }
96 }
97 }
98
99 fn render(&self, stdout: &mut impl Write, input: &str, cursor_pos: usize) -> Result<()> {
100 queue!(
101 stdout,
102 cursor::MoveToColumn(0),
103 terminal::Clear(ClearType::CurrentLine),
104 Print(&self.prompt),
105 Print(" "),
106 Print(input),
107 )?;
108
109 let col = (self.prompt.len() + 1 + cursor_pos) as u16;
110 queue!(stdout, cursor::MoveToColumn(col))?;
111
112 stdout.flush()?;
113 Ok(())
114 }
115}