term_rs/
terminal.rs

1use pancurses::{Window, initscr, noecho, Input, resize_term};
2use super::command::CommandHistory;
3
4#[derive(Copy, Clone, Debug, PartialOrd, PartialEq)]
5struct Position(i32, i32);
6
7pub struct Terminal<F> {
8    prompt: String,
9    window: Window,
10    history: CommandHistory,
11    buf: Vec<u8>,
12    pos: i32,
13    process: F,
14}
15
16impl<F> Terminal<F>
17    where F: Fn(String) -> String {
18    pub fn run(process: F) {
19        let window = initscr();
20        window.keypad(true);
21        window.scrollok(true);
22        window.setscrreg(0, window.get_max_y());
23        noecho();
24        let mut t = Terminal {
25            prompt: "debug> ".to_owned(),
26            window,
27            history: CommandHistory::default(),
28            buf: Vec::new(),
29            pos: 0,
30            process,
31        };
32        loop {
33            let command = t.input();
34            let result = (t.process)(command);
35            t.window.printw(format!("{}\n", result));
36        }
37    }
38
39    fn print_prompt(&self) {
40        self.window.printw(self.prompt.as_str());
41    }
42
43    fn input(&mut self) -> String {
44        self.print_prompt();
45        self.pos = 0;
46        loop {
47            if let Some(ch) = self.window.getch() {
48                match ch {
49                    Input::Character(c) => {
50                        match c {
51                            '\n' => { return self.line_feed(); }
52                            '\t' => {}
53                            '\u{7f}' => { self.backspace(); }
54                            '\u{15}' => {
55                                // ctrl+U
56                                self.clear_to_start();
57                            }
58                            '\u{c}' => {
59                                // ctrl+L
60                                self.clear_line();
61                            }
62                            '\u{1}' => {
63                                // ctl+A
64                                self.move_to_start();
65                            }
66                            '\u{5}' => {
67                                // ctrl+E
68                                self.move_to_end();
69                            }
70                            x if (x as u8) >= 0x20 && (x as u8) <= 0x7E => { self.insert(x.to_string()); }
71                            _ => {}
72                        }
73                    }
74                    Input::KeyBackspace => {self.backspace();}
75                    Input::KeyResize => { self.on_resized(); }
76                    Input::KeyUp => { self.prev_command(); }
77                    Input::KeyDown => { self.next_command(); }
78                    Input::KeyLeft => { self.move_left(); }
79                    Input::KeyRight => { self.move_right(); }
80                    x => { println!("{:?}", x); }
81                }
82            }
83        }
84    }
85
86    fn on_resized(&mut self) {
87        resize_term(0, 0);
88        self.window.setscrreg(0, self.window.get_max_y());
89    }
90
91    fn line_feed(&mut self) -> String {
92        let ret = String::from_utf8(self.buf.clone()).unwrap();
93        self.clear_line();
94        self.window.printw(format!("{}\n", ret));
95        if ret.trim().len() > 0 {
96            self.history.add_command(ret.clone());
97        }
98        self.pos = 0;
99        return ret;
100    }
101
102    fn prev_command(&mut self) {
103        if self.history.at_top() {
104            let command = String::from_utf8(self.buf.clone()).unwrap();
105            self.history.add_command(command);
106            self.history.prev_command();
107        }
108        self.clear_line();
109        if let Some(command) = self.history.prev_command() {
110            self.buf.extend(command.as_bytes());
111            self.window.printw(command);
112        }
113        self.pos = self.buf.len() as i32;
114    }
115
116    fn next_command(&mut self) {
117        self.clear_line();
118        if let Some(command) = self.history.next_command() {
119            self.buf.extend(command.as_bytes());
120            self.window.printw(command);
121        }
122        self.pos = self.buf.len() as i32;
123    }
124
125    fn insert(&mut self, text: String) {
126        if self.pos == self.buf.len() as i32 {
127            self.buf.extend(text.as_bytes());
128            self.pos += text.as_bytes().len() as i32;
129            self.window.printw(text);
130        } else {
131            let tmp = {
132                let pre = &self.buf[0..self.pos as usize];
133                let end = &self.buf[self.pos as usize..];
134                let mut tmp = Vec::new();
135                tmp.extend(pre);
136                tmp.extend(text.as_bytes());
137                tmp.extend(end);
138                tmp
139            };
140            let len = text.as_bytes().len() as i32;
141            let pos = self.pos + len;
142            for _ in 0..len {
143                self.move_right();
144            }
145            let position = self.current_position();
146            self.clear_line();
147            self.buf = tmp;
148            self.window.printw(String::from_utf8(self.buf.clone()).unwrap());
149            self.pos = pos;
150            self.window.mv(position.1, position.0);
151        }
152
153    }
154
155    fn clear_to_start(&mut self) {
156        let tmp = self.buf[self.pos as usize..].to_owned();
157        let origin = self.line_start_position();
158        self.clear_line();
159        self.buf = tmp;
160        self.window.printw(String::from_utf8(self.buf.clone()).unwrap());
161        self.window.mv(origin.1, origin.0);
162    }
163
164    fn backspace(&mut self) {
165        if self.pos == 0 {
166
167        } else if self.pos == self.buf.len() as i32 {
168            self.move_left();
169            self.window.delch();
170            self.buf.pop();
171        } else {
172            self.move_left();
173            self.buf.remove(self.pos as usize);
174            let p = self.current_position();
175            let pos = self.pos;
176            let tmp = self.buf.clone();
177            self.clear_line();
178            self.buf = tmp;
179            self.window.printw(String::from_utf8(self.buf.clone()).unwrap());
180            self.window.mv(p.1, p.0);
181            self.pos = pos;
182        }
183    }
184
185    fn clear_line(&mut self) {
186        let end_y = self.line_end_position().1;
187        let start_y = self.line_start_position().1;
188        let mut y = end_y;
189        while y >= start_y {
190            self.window.mv(y, 0);
191            self.window.deleteln();
192            y -= 1;
193        }
194        self.buf.clear();
195        self.print_prompt();
196        self.pos = 0;
197        debug_assert_eq!(self.line_start_position(), self.current_position());
198    }
199
200    fn current_position(&self) -> Position {
201        Position(self.window.get_cur_x(), self.window.get_cur_y())
202    }
203
204    fn move_left(&mut self) {
205        if self.pos > 0 {
206            let Position(x, y) = self.current_position();
207            if x == 0 {
208                self.window.mv(y - 1, self.window.get_max_x() - 1);
209            } else {
210                self.window.mv(y, x - 1);
211            }
212            self.pos -= 1;
213        }
214    }
215
216    fn move_right(&mut self) {
217        if self.pos < self.buf.len() as i32 {
218            let Position(x, y) = self.current_position();
219            if x == self.window.get_max_x() - 1 {
220                self.window.mv(y + 1, 0);
221            } else {
222                self.window.mv(y, x + 1);
223            }
224            self.pos += 1;
225        }
226    }
227
228    fn move_to_start(&mut self) {
229        let Position(x, y) = self.line_start_position();
230        self.window.mv(y, x);
231        self.pos = 0;
232    }
233
234    fn move_to_end(&mut self) {
235        let Position(x, y) = self.line_end_position();
236        self.window.mv(y, x);
237        self.pos = self.buf.len() as i32;
238    }
239
240    fn line_start_position(&self) -> Position {
241        let y = self.window.get_cur_y();
242        let column = self.window.get_max_x();
243        let line_count = (self.pos + 1 - (column - self.prompt.len() as i32) + column - 1) / column + 1;
244        Position(self.prompt.len() as i32, y - line_count + 1)
245    }
246
247    fn line_end_position(&self) -> Position {
248        let data_len = self.buf.len() as i32;
249        let column = self.window.get_max_x();
250        let Position(x, y) = self.line_start_position();
251        if data_len <= column - self.prompt.len() as i32 {
252            Position(x + data_len, y)
253        } else {
254            let line_count = (data_len - (column - self.prompt.len() as i32) + column - 1) / column + 1;
255            let end_x = (data_len - (column - self.prompt.len() as i32)) % column;
256            let end_y = y + line_count - 1;
257            Position(end_x, end_y)
258        }
259    }
260
261    #[allow(dead_code)]
262    fn debug_print_buf(&self) {
263        println!("\nbuf: {}, {}", String::from_utf8(self.buf.clone()).unwrap(), self.buf.len());
264    }
265
266    #[allow(dead_code)]
267    fn debug_print_current_position(&self) {
268        println!("\n{:?}", self.current_position());
269    }
270
271    #[allow(dead_code)]
272    fn debug_print_pos(&self) {
273        print!("\npos: {}", self.pos);
274    }
275}
276