xacli_components/components/
select.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 Select<T> {
14    prompt: String,
15    options: Vec<(String, T)>,
16}
17
18impl<T> Select<T> {
19    pub fn new(prompt: impl Into<String>) -> Self {
20        Self {
21            prompt: prompt.into(),
22            options: Vec::new(),
23        }
24    }
25
26    pub fn option(mut self, label: impl Into<String>, value: T) -> Self {
27        self.options.push((label.into(), value));
28        self
29    }
30
31    pub fn options(mut self, options: Vec<(String, T)>) -> Self {
32        self.options = options;
33        self
34    }
35
36    pub fn run(self) -> Result<T> {
37        if self.options.is_empty() {
38            return Err(ComponentError::InvalidInput("No options provided".into()));
39        }
40
41        let mut stdout = stdout();
42        terminal::enable_raw_mode()?;
43
44        let result = self.run_inner(&mut stdout);
45
46        terminal::disable_raw_mode()?;
47
48        result
49    }
50
51    fn run_inner(mut self, stdout: &mut impl Write) -> Result<T> {
52        let mut selected = 0;
53        let mut first_render = true;
54
55        self.render(stdout, selected, first_render)?;
56        first_render = false;
57
58        loop {
59            if let Event::Key(key) = event::read()? {
60                match key.code {
61                    KeyCode::Enter => {
62                        // 清除渲染
63                        self.clear_render(stdout)?;
64                        // Use \r\n to properly handle raw mode
65                        queue!(
66                            stdout,
67                            cursor::MoveToColumn(0),
68                            Print("✓ "),
69                            Print(&self.options[selected].0),
70                            Print("\r\n")
71                        )?;
72                        stdout.flush()?;
73                        return Ok(self.options.remove(selected).1);
74                    }
75                    KeyCode::Esc => {
76                        self.clear_render(stdout)?;
77                        return Err(ComponentError::Interrupted);
78                    }
79                    KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
80                        self.clear_render(stdout)?;
81                        return Err(ComponentError::Interrupted);
82                    }
83                    KeyCode::Up | KeyCode::Char('k') => {
84                        if selected > 0 {
85                            selected -= 1;
86                            self.render(stdout, selected, first_render)?;
87                        }
88                    }
89                    KeyCode::Down | KeyCode::Char('j') => {
90                        if selected < self.options.len() - 1 {
91                            selected += 1;
92                            self.render(stdout, selected, first_render)?;
93                        }
94                    }
95                    KeyCode::Home => {
96                        selected = 0;
97                        self.render(stdout, selected, first_render)?;
98                    }
99                    KeyCode::End => {
100                        selected = self.options.len() - 1;
101                        self.render(stdout, selected, first_render)?;
102                    }
103                    _ => {}
104                }
105            }
106        }
107    }
108
109    fn render(&self, stdout: &mut impl Write, selected: usize, first_render: bool) -> Result<()> {
110        if !first_render {
111            // 清除之前的渲染
112            for _ in 0..self.options.len() + 1 {
113                queue!(
114                    stdout,
115                    cursor::MoveUp(1),
116                    terminal::Clear(ClearType::CurrentLine),
117                )?;
118            }
119        }
120
121        queue!(stdout, cursor::MoveToColumn(0))?;
122
123        // 提示
124        queue!(stdout, Print(&self.prompt), Print("\r\n"))?;
125
126        // 选项
127        for (i, (label, _)) in self.options.iter().enumerate() {
128            let marker = if i == selected { ">" } else { " " };
129            queue!(
130                stdout,
131                cursor::MoveToColumn(0),
132                Print(marker),
133                Print(" "),
134                Print(label),
135                Print("\r\n"),
136            )?;
137        }
138
139        stdout.flush()?;
140        Ok(())
141    }
142
143    fn clear_render(&self, stdout: &mut impl Write) -> Result<()> {
144        for _ in 0..self.options.len() + 1 {
145            queue!(
146                stdout,
147                cursor::MoveUp(1),
148                terminal::Clear(ClearType::CurrentLine),
149            )?;
150        }
151        queue!(stdout, cursor::MoveToColumn(0))?;
152        stdout.flush()?;
153        Ok(())
154    }
155}