use super::{display::draw, ev_handler::handle_event, events::Event, term};
use crate::{error::MinusError, input::InputEvent, Pager, PagerState};
use crossbeam_channel::{Receiver, Sender, TrySendError};
use crossterm::event;
use once_cell::sync::OnceCell;
use std::io::{stdout, Stdout};
#[cfg(feature = "search")]
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::{
cell::RefCell,
sync::{Arc, Mutex},
};
#[cfg(feature = "static_output")]
use {super::display::write_lines, crossterm::tty::IsTty};
#[derive(PartialEq)]
pub enum RunMode {
#[cfg(feature = "static_output")]
Static,
#[cfg(feature = "dynamic_output")]
Dynamic,
}
pub static RUNMODE: OnceCell<RunMode> = OnceCell::new();
#[allow(clippy::module_name_repetitions)]
pub fn init_core(mut pager: Pager) -> std::result::Result<(), MinusError> {
let mut out = stdout();
#[cfg(feature = "search")]
let input_thread_running = Arc::new(AtomicBool::new(true));
#[allow(unused_mut)]
let mut ps = generate_initial_state(&mut pager.rx, &mut out)?;
#[cfg(feature = "static_output")]
if RUNMODE.get() == Some(&RunMode::Static) {
if !out.is_tty() {
write_lines(&mut out, &mut ps)?;
return Ok(());
}
if ps.num_lines() <= ps.rows && ps.run_no_overflow {
write_lines(&mut out, &mut ps)?;
ps.exit();
return Ok(());
}
}
term::setup(&out)?;
let ps_mutex = Arc::new(Mutex::new(ps));
let evtx = pager.tx.clone();
let rx = pager.rx.clone();
let out = stdout();
let p1 = ps_mutex.clone();
#[cfg(feature = "search")]
let input_thread_running2 = input_thread_running.clone();
thread::spawn(move || {
event_reader(
&evtx,
&p1,
#[cfg(feature = "search")]
&input_thread_running2,
)
});
start_reactor(
&rx,
&ps_mutex,
out,
#[cfg(feature = "search")]
&input_thread_running,
)?;
Ok(())
}
#[allow(clippy::too_many_lines)]
fn start_reactor(
rx: &Receiver<Event>,
ps: &Arc<Mutex<PagerState>>,
mut out: Stdout,
#[cfg(feature = "search")] input_thread_running: &Arc<AtomicBool>,
) -> Result<(), MinusError> {
let is_exitted: RefCell<bool> = RefCell::new(false);
{
let mut p = ps.lock().unwrap();
draw(&mut out, &mut p)?;
}
let out = RefCell::new(out);
#[cfg(any(feature = "dynamic_output"))]
let dynamic_matcher = || -> Result<(), MinusError> {
use std::{convert::TryInto, io::Write};
loop {
if *is_exitted.borrow() {
break;
}
let event = rx.try_recv();
#[allow(clippy::unnested_or_patterns)]
match event {
Ok(ev) if ev.required_immidiate_screen_update() => {
let mut p = ps.lock().unwrap();
handle_event(
ev,
&mut *out.borrow_mut(),
&mut p,
&mut is_exitted.borrow_mut(),
#[cfg(feature = "search")]
input_thread_running,
)?;
draw(&mut *out.borrow_mut(), &mut p)?;
}
Ok(Event::SetPrompt(ref text)) | Ok(Event::SendMessage(ref text)) => {
let mut p = ps.lock().unwrap();
let fmt_text = crate::wrap_str(text, p.cols);
let mut out = out.borrow_mut();
if let Ok(Event::SetPrompt(_)) = event {
p.prompt = fmt_text.clone();
} else {
p.message = Some(fmt_text.clone());
}
term::move_cursor(&mut *out, 0, p.rows.try_into().unwrap(), false)?;
super::display::write_prompt(
&mut *out,
fmt_text.first().unwrap(),
p.rows.try_into().unwrap(),
)?;
}
Ok(Event::AppendData(text)) => {
let mut p = ps.lock().unwrap();
let mut fmt_text = p.make_append_str(&text);
if p.num_lines() < p.rows {
let mut out = out.borrow_mut();
term::move_cursor(&mut *out, 0, p.num_lines().try_into().unwrap(), false)?;
let available_rows = p.rows.saturating_sub(p.num_lines().saturating_add(1));
let num_appendable = fmt_text.len().min(available_rows);
write!(out, "{}", fmt_text[0..num_appendable].join("\n\r"))?;
out.flush()?;
}
p.formatted_lines.append(&mut fmt_text);
}
Ok(ev) => {
let mut p = ps.lock().unwrap();
handle_event(
ev,
&mut *out.borrow_mut(),
&mut p,
&mut is_exitted.borrow_mut(),
#[cfg(feature = "search")]
input_thread_running,
)?;
}
Err(_) => {}
}
}
Ok(())
};
#[cfg(feature = "static_output")]
let static_matcher = || -> Result<(), MinusError> {
loop {
if *is_exitted.borrow() {
break;
}
if let Ok(Event::UserInput(inp)) = rx.try_recv() {
let mut p = ps.lock().unwrap();
handle_event(
Event::UserInput(inp),
&mut *out.borrow_mut(),
&mut p,
&mut is_exitted.borrow_mut(),
#[cfg(feature = "search")]
input_thread_running,
)?;
draw(&mut *out.borrow_mut(), &mut p)?;
}
}
Ok(())
};
#[allow(clippy::match_same_arms)]
match RUNMODE.get() {
#[cfg(feature = "dynamic_output")]
Some(&RunMode::Dynamic) => dynamic_matcher()?,
#[cfg(feature = "static_output")]
Some(&RunMode::Static) => static_matcher()?,
None => panic!("Static variable RUNMODE not set"),
}
Ok(())
}
fn generate_initial_state(
rx: &mut Receiver<Event>,
mut out: &mut Stdout,
) -> Result<PagerState, MinusError> {
let mut ps = PagerState::new()?;
rx.try_iter().try_for_each(|ev| -> Result<(), MinusError> {
handle_event(
ev,
&mut out,
&mut ps,
&mut false,
#[cfg(feature = "search")]
&Arc::new(AtomicBool::new(true)),
)
})?;
Ok(ps)
}
fn event_reader(
evtx: &Sender<Event>,
ps: &Arc<Mutex<PagerState>>,
#[cfg(feature = "search")] input_thread_running: &Arc<AtomicBool>,
) -> Result<(), MinusError> {
loop {
#[cfg(feature = "search")]
if !input_thread_running.load(Ordering::SeqCst) {
continue;
}
if event::poll(std::time::Duration::from_millis(10))
.map_err(|e| MinusError::HandleEvent(e.into()))?
{
let ev = event::read().map_err(|e| MinusError::HandleEvent(e.into()))?;
let mut guard = ps.lock().unwrap();
let input = guard.input_classifier.classify_input(ev, &guard);
if let Some(iev) = input {
if let InputEvent::Number(n) = iev {
guard.prefix_num.push(n);
continue;
}
guard.prefix_num.clear();
if let Err(TrySendError::Disconnected(_)) = evtx.try_send(Event::UserInput(iev)) {
break;
}
} else {
guard.prefix_num.clear();
}
}
}
Result::<(), MinusError>::Ok(())
}