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 self.clear_to_start();
57 }
58 '\u{c}' => {
59 self.clear_line();
61 }
62 '\u{1}' => {
63 self.move_to_start();
65 }
66 '\u{5}' => {
67 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