use crate::shell::input_thread::{input_thread, InputEvent};
use crate::shell::os::{clear_remaining, get_window_size, move_cursor, write, Terminal};
use crate::shell_println;
use std::sync::mpsc;
use std::thread::JoinHandle;
pub enum Event {
CommandReceived(String),
ExitRequested,
}
pub trait SendChannel: Send + 'static {
fn send(&self, event: Event);
}
pub struct Shell {
_os: Terminal,
input_thread: JoinHandle<()>,
app_thread: JoinHandle<()>,
_send_ch: mpsc::Sender<InputEvent>,
}
fn print_prompt(row: i32, prompt: &'static str) {
move_cursor(0, row);
write(prompt);
move_cursor(prompt.len() as _, row);
}
enum Window {
StartEnd(usize, usize),
Start(usize),
Full,
}
fn string_window(pos: usize, col: i32, prompt: &'static str, str: &str) -> Window {
let maxsize = col as usize - prompt.len() - 1;
if str.len() > maxsize {
if pos >= str.len() {
Window::Start(str.len() - maxsize)
} else {
let mut start = pos;
let mut end = pos + maxsize;
while end >= str.len() {
end -= 1;
start = start.saturating_sub(1);
}
Window::StartEnd(start, end)
}
} else {
Window::Full
}
}
fn reset_string(pos: usize, col: i32, row: i32, prompt: &'static str, str: &str) {
print_prompt(row, prompt);
let window = string_window(pos, col, prompt, str);
match window {
Window::Start(start) => write(&str[start..]),
Window::StartEnd(start, end) => write(&str[start..end]),
Window::Full => write(str),
}
clear_remaining();
}
fn move_to_pos(pos: usize, col: i32, row: i32, prompt: &'static str, str: &str) {
let window = string_window(pos, col, prompt, str);
match window {
Window::StartEnd(start, _) => move_cursor((prompt.len() + (pos - start)) as _, row),
Window::Start(start) => move_cursor((prompt.len() + (pos - start)) as _, row),
Window::Full => move_cursor((prompt.len() + pos) as _, row),
}
}
fn application_thread<T: SendChannel>(
prompt: &'static str,
recv_ch: mpsc::Receiver<InputEvent>,
master_send_ch: T,
) {
let mut history = Vec::new();
let mut hindex = 0;
let mut cur_line = String::new();
let (col, row) = get_window_size();
let mut pos = 0;
print_prompt(row, prompt);
loop {
let msg = recv_ch.recv().unwrap();
match msg {
InputEvent::End => {
master_send_ch.send(Event::ExitRequested);
break;
}
InputEvent::NewLine => {
write("\n");
print_prompt(row, prompt);
history.push(cur_line.clone());
hindex = history.len();
master_send_ch.send(Event::CommandReceived(cur_line.clone()));
cur_line.clear();
pos = 0;
}
InputEvent::Complete => {
shell_println!("Not yet implemented");
}
InputEvent::HistoryPrev => {
if history.is_empty() {
continue;
}
hindex = hindex.saturating_sub(1);
let msg = &history[hindex];
cur_line = msg.clone();
pos = cur_line.len();
reset_string(pos, col, row, prompt, &cur_line);
}
InputEvent::HistoryNext => {
if history.is_empty() {
continue;
}
if hindex != history.len() {
hindex += 1;
}
if hindex == history.len() {
reset_string(0, col, row, prompt, "");
cur_line.clear();
pos = 0;
continue;
}
let msg = &history[hindex];
cur_line = msg.clone();
pos = cur_line.len();
reset_string(pos, col, row, prompt, &cur_line);
}
InputEvent::LineStart => {
pos = 0;
reset_string(pos, col, row, prompt, &cur_line);
move_to_pos(pos, col, row, prompt, &cur_line);
}
InputEvent::LineEnd => {
pos = cur_line.len();
reset_string(pos, col, row, prompt, &cur_line);
move_to_pos(pos, col, row, prompt, &cur_line);
}
InputEvent::Input(s) => {
cur_line.insert_str(pos, &s);
pos += s.len();
reset_string(pos, col, row, prompt, &cur_line);
move_to_pos(pos, col, row, prompt, &cur_line);
}
InputEvent::Left => {
if pos == 0 {
continue;
}
pos -= 1;
reset_string(pos, col, row, prompt, &cur_line);
move_to_pos(pos, col, row, prompt, &cur_line);
}
InputEvent::Right => {
if pos >= cur_line.len() {
continue;
}
pos += 1;
reset_string(pos, col, row, prompt, &cur_line);
move_to_pos(pos, col, row, prompt, &cur_line);
}
InputEvent::Delete => {
if pos == 0 {
continue;
}
cur_line.remove(pos - 1);
pos -= 1;
reset_string(pos, col, row, prompt, &cur_line);
move_to_pos(pos, col, row, prompt, &cur_line);
}
}
}
}
impl Shell {
pub fn new<T: SendChannel>(prompt: &'static str, master_send_ch: T) -> Self {
let (send_ch, recv_ch) = mpsc::channel();
let motherfuckingrust = send_ch.clone();
let input_thread = std::thread::spawn(|| {
input_thread(motherfuckingrust);
});
let app_thread = std::thread::spawn(move || {
application_thread(prompt, recv_ch, master_send_ch);
});
Self {
_os: Terminal::new(),
input_thread,
app_thread,
_send_ch: send_ch,
}
}
pub fn exit(self) {
#[cfg(unix)]
{
use std::os::unix::thread::JoinHandleExt;
extern "C" fn useless() {}
let mut sig2: std::mem::MaybeUninit<libc::sigaction> = std::mem::MaybeUninit::uninit();
let mut sig: libc::sigaction = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
sig.sa_sigaction = useless as _;
unsafe { libc::sigaction(libc::SIGUSR2, &sig as _, sig2.as_mut_ptr()) };
let pthread = self.input_thread.as_pthread_t();
unsafe { libc::pthread_kill(pthread, libc::SIGUSR2) };
self.input_thread.join().unwrap();
self.app_thread.join().unwrap();
unsafe { libc::sigaction(libc::SIGUSR2, sig2.as_ptr(), std::ptr::null_mut()) };
}
#[cfg(windows)]
{
let handle = unsafe {
windows_sys::Win32::System::Console::GetStdHandle(
windows_sys::Win32::System::Console::STD_INPUT_HANDLE,
)
};
unsafe { windows_sys::Win32::System::IO::CancelIoEx(handle, std::ptr::null()) };
self.input_thread.join().unwrap();
self.app_thread.join().unwrap();
}
}
}