1use crate::shell::input_thread::{input_thread, InputEvent};
30use crate::shell::os::{clear_remaining, get_window_size, move_cursor, write, Terminal};
31use crate::shell_println;
32use std::sync::mpsc;
33use std::thread::JoinHandle;
34
35pub enum Event {
39 CommandReceived(String),
41
42 ExitRequested,
44}
45
46pub trait SendChannel: Send + 'static {
48 fn send(&self, event: Event);
56}
57
58pub struct Shell {
60 _os: Terminal,
61 input_thread: JoinHandle<()>,
62 app_thread: JoinHandle<()>,
63 _send_ch: mpsc::Sender<InputEvent>,
64}
65
66fn print_prompt(row: i32, prompt: &'static str) {
67 move_cursor(0, row);
68 write(prompt);
69 move_cursor(prompt.len() as _, row);
70}
71
72enum Window {
73 StartEnd(usize, usize),
74 Start(usize),
75 Full,
76}
77
78fn string_window(pos: usize, col: i32, prompt: &'static str, str: &str) -> Window {
79 let maxsize = col as usize - prompt.len() - 1;
80 if str.len() > maxsize {
81 if pos >= str.len() {
82 Window::Start(str.len() - maxsize)
83 } else {
84 let mut start = pos;
85 let mut end = pos + maxsize;
86 while end >= str.len() {
87 end -= 1;
88 start = start.saturating_sub(1);
89 }
90 Window::StartEnd(start, end)
91 }
92 } else {
93 Window::Full
94 }
95}
96
97fn reset_string(pos: usize, col: i32, row: i32, prompt: &'static str, str: &str) {
98 print_prompt(row, prompt);
99 let window = string_window(pos, col, prompt, str);
100 match window {
101 Window::Start(start) => write(&str[start..]),
102 Window::StartEnd(start, end) => write(&str[start..end]),
103 Window::Full => write(str),
104 }
105 clear_remaining();
106}
107
108fn move_to_pos(pos: usize, col: i32, row: i32, prompt: &'static str, str: &str) {
109 let window = string_window(pos, col, prompt, str);
110 match window {
111 Window::StartEnd(start, _) => move_cursor((prompt.len() + (pos - start)) as _, row),
112 Window::Start(start) => move_cursor((prompt.len() + (pos - start)) as _, row),
113 Window::Full => move_cursor((prompt.len() + pos) as _, row),
114 }
115}
116
117fn application_thread<T: SendChannel>(
118 prompt: &'static str,
119 recv_ch: mpsc::Receiver<InputEvent>,
120 master_send_ch: T,
121) {
122 let mut history = Vec::new();
123 let mut hindex = 0;
124 let mut cur_line = String::new();
125 let (col, row) = get_window_size();
126 let mut pos = 0;
127 print_prompt(row, prompt);
128 loop {
129 let msg = recv_ch.recv().unwrap();
130 match msg {
131 InputEvent::End => {
132 master_send_ch.send(Event::ExitRequested);
133 break;
134 }
135 InputEvent::NewLine => {
136 write("\n");
137 print_prompt(row, prompt);
138 history.push(cur_line.clone());
139 hindex = history.len();
140 master_send_ch.send(Event::CommandReceived(cur_line.clone()));
141 cur_line.clear();
142 pos = 0;
143 }
144 InputEvent::Complete => {
145 shell_println!("Not yet implemented");
146 }
147 InputEvent::HistoryPrev => {
148 if history.is_empty() {
149 continue;
150 }
151 hindex = hindex.saturating_sub(1);
152 let msg = &history[hindex];
153 cur_line = msg.clone();
154 pos = cur_line.len();
155 reset_string(pos, col, row, prompt, &cur_line);
156 }
157 InputEvent::HistoryNext => {
158 if history.is_empty() {
159 continue;
160 }
161 if hindex != history.len() {
162 hindex += 1;
163 }
164 if hindex == history.len() {
165 reset_string(0, col, row, prompt, "");
166 cur_line.clear();
167 pos = 0;
168 continue;
169 }
170 let msg = &history[hindex];
171 cur_line = msg.clone();
172 pos = cur_line.len();
173 reset_string(pos, col, row, prompt, &cur_line);
174 }
175 InputEvent::LineStart => {
176 pos = 0;
177 reset_string(pos, col, row, prompt, &cur_line);
178 move_to_pos(pos, col, row, prompt, &cur_line);
179 }
180 InputEvent::LineEnd => {
181 pos = cur_line.len();
182 reset_string(pos, col, row, prompt, &cur_line);
183 move_to_pos(pos, col, row, prompt, &cur_line);
184 }
185 InputEvent::Input(s) => {
186 cur_line.insert_str(pos, &s);
187 pos += s.len();
188 reset_string(pos, col, row, prompt, &cur_line);
189 move_to_pos(pos, col, row, prompt, &cur_line);
190 }
191 InputEvent::Left => {
192 if pos == 0 {
193 continue;
194 }
195 pos -= 1;
196 reset_string(pos, col, row, prompt, &cur_line);
197 move_to_pos(pos, col, row, prompt, &cur_line);
198 }
199 InputEvent::Right => {
200 if pos >= cur_line.len() {
201 continue;
202 }
203 pos += 1;
204 reset_string(pos, col, row, prompt, &cur_line);
205 move_to_pos(pos, col, row, prompt, &cur_line);
206 }
207 InputEvent::Delete => {
208 if pos == 0 {
209 continue;
210 }
211 cur_line.remove(pos - 1);
212 pos -= 1;
213 reset_string(pos, col, row, prompt, &cur_line);
214 move_to_pos(pos, col, row, prompt, &cur_line);
215 }
216 }
217 }
218}
219
220impl Shell {
221 pub fn new<T: SendChannel>(prompt: &'static str, master_send_ch: T) -> Self {
232 let (send_ch, recv_ch) = mpsc::channel();
233 let motherfuckingrust = send_ch.clone();
234 let input_thread = std::thread::spawn(|| {
235 input_thread(motherfuckingrust);
236 });
237 let app_thread = std::thread::spawn(move || {
238 application_thread(prompt, recv_ch, master_send_ch);
239 });
240 Self {
241 _os: Terminal::new(),
242 input_thread,
243 app_thread,
244 _send_ch: send_ch,
245 }
246 }
247
248 pub fn exit(self) {
250 #[cfg(unix)]
252 {
253 use std::os::unix::thread::JoinHandleExt;
255
256 extern "C" fn useless() {}
258 let mut sig2: std::mem::MaybeUninit<libc::sigaction> = std::mem::MaybeUninit::uninit();
259 let mut sig: libc::sigaction = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
260 sig.sa_sigaction = useless as _;
261 unsafe { libc::sigaction(libc::SIGUSR2, &sig as _, sig2.as_mut_ptr()) };
262
263 let pthread = self.input_thread.as_pthread_t();
265 unsafe { libc::pthread_kill(pthread, libc::SIGUSR2) };
266
267 self.input_thread.join().unwrap();
269 self.app_thread.join().unwrap();
270
271 unsafe { libc::sigaction(libc::SIGUSR2, sig2.as_ptr(), std::ptr::null_mut()) };
274 }
275 #[cfg(windows)]
276 {
277 let handle = unsafe {
279 windows_sys::Win32::System::Console::GetStdHandle(
280 windows_sys::Win32::System::Console::STD_INPUT_HANDLE,
281 )
282 };
283 unsafe { windows_sys::Win32::System::IO::CancelIoEx(handle, std::ptr::null()) };
284
285 self.input_thread.join().unwrap();
287 self.app_thread.join().unwrap();
288 }
289 }
290}