xacli_components/components/
input.rs

1use 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!(); // 换行
39
40        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        // 显示提示
48        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}