use crate::{
error::MinusError,
events::Event,
utils::{draw, ev_handler::handle_event, term::setup},
Pager, PagerState,
};
use crossbeam_channel::Receiver;
#[cfg(any(
feature = "async_output",
feature = "static_output",
feature = "threads_output"
))]
use once_cell::sync::OnceCell;
use std::io::stdout;
use std::io::Stdout;
#[cfg(feature = "search")]
use std::sync::atomic::AtomicBool;
use std::{
cell::RefCell,
sync::{Arc, Mutex},
};
#[cfg(any(feature = "static_output", feature = "threads_output"))]
use {crate::input::reader::polling, std::thread};
#[cfg(feature = "async_output")]
use {crate::input::reader::streaming, futures_lite::future};
#[cfg(feature = "static_output")]
use {crate::utils::write_lines, crossterm::tty::IsTty};
#[cfg(any(
feature = "async_output",
feature = "static_output",
feature = "threads_output"
))]
pub(crate) enum RunMode {
#[cfg(feature = "static_output")]
Static,
#[cfg(feature = "async_output")]
Async,
#[cfg(feature = "threads_output")]
Thread,
}
#[cfg(any(
feature = "async_output",
feature = "static_output",
feature = "threads_output"
))]
pub(crate) static RUNMODE: OnceCell<RunMode> = OnceCell::new();
#[cfg(any(
feature = "async_output",
feature = "static_output",
feature = "threads_output"
))]
#[allow(clippy::needless_pass_by_value)]
pub(crate) 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 !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(());
}
}
setup(&out)?;
let ps_mutex = Arc::new(Mutex::new(ps));
#[cfg(any(feature = "static_output", feature = "threads_output"))]
let start_no_async = || -> Result<(), MinusError> {
let evtx = pager.tx.clone();
let rx = pager.rx.clone();
let out = stdout();
let p1 = ps_mutex.clone();
#[cfg(feature = "search")]
let input_thread_running = input_thread_running.clone();
#[cfg(feature = "search")]
let input_thread_running2 = input_thread_running.clone();
thread::spawn(move || {
polling(
&evtx,
&p1,
#[cfg(feature = "search")]
&input_thread_running2,
)
});
start_reactor(
&rx,
&ps_mutex,
out,
#[cfg(feature = "search")]
&input_thread_running,
)?;
Ok(())
};
#[cfg(feature = "async_output")]
let start_async = || -> Result<(), MinusError> {
let evtx = pager.tx.clone();
let rx = pager.rx.clone();
let out = stdout();
let p1 = ps_mutex.clone();
let p2 = p1.clone();
#[cfg(feature = "search")]
let input_thread_running = input_thread_running.clone();
#[cfg(feature = "search")]
let input_thread_running2 = input_thread_running.clone();
let input_reader = async_global_executor::spawn(streaming(
evtx,
p2,
#[cfg(feature = "search")]
input_thread_running2,
));
let reactor = async_global_executor::spawn_blocking(move || {
start_reactor(
&rx,
&p1,
out,
#[cfg(feature = "search")]
&input_thread_running,
)
});
let task = future::zip(input_reader, reactor);
let (res1, res2) = async_global_executor::block_on(task);
res1?;
res2?;
Ok(())
};
#[allow(clippy::match_same_arms)]
match RUNMODE.get() {
#[cfg(feature = "threads_output")]
Some(&RunMode::Thread) => start_no_async(),
#[cfg(feature = "static_output")]
Some(&RunMode::Static) => start_no_async(),
#[cfg(feature = "async_output")]
Some(&RunMode::Async) => start_async(),
None => panic!("RUNMODE not set"),
}
}
#[cfg(any(
feature = "async_output",
feature = "static_output",
feature = "threads_output"
))]
fn start_reactor(
rx: &Receiver<Event>,
ps: &Arc<Mutex<PagerState>>,
mut out: Stdout,
#[cfg(feature = "search")] input_thread_running: &Arc<AtomicBool>,
) -> Result<(), MinusError> {
#[cfg(any(feature = "async_output", feature = "threads_output"))]
let mut filled = false;
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 = "async_output", feature = "threads_output"))]
let mut dynamic_matcher = || -> Result<(), MinusError> {
loop {
if *is_exitted.borrow() {
break;
}
match rx.try_recv() {
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::AppendData(text)) => {
let mut p = ps.lock().unwrap();
handle_event(
Event::AppendData(text),
&mut *out.borrow_mut(),
&mut p,
&mut is_exitted.borrow_mut(),
#[cfg(feature = "search")]
input_thread_running,
)?;
if p.num_lines() > p.rows {
if !filled || p.message.1 {
draw(&mut *out.borrow_mut(), &mut p)?;
filled = true;
if p.message.1 {
p.message.1 = false;
}
}
}
if p.num_lines() < p.rows || p.message.1 {
draw(&mut *out.borrow_mut(), &mut p)?;
if p.message.1 {
p.message.1 = false;
}
}
}
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 = "async_output")]
Some(&RunMode::Async) => dynamic_matcher()?,
#[cfg(feature = "threads_output")]
Some(&RunMode::Thread) => dynamic_matcher()?,
#[cfg(feature = "static_output")]
Some(&RunMode::Static) => static_matcher()?,
None => panic!("RUNMODE not set"),
}
Ok(())
}
#[cfg(any(
feature = "async_output",
feature = "static_output",
feature = "threads_output"
))]
fn generate_initial_state(
rx: &mut Receiver<Event>,
mut out: &mut Stdout,
) -> Result<PagerState, MinusError> {
let mut ps = PagerState::new()?;
#[cfg(feature = "search")]
let input_thread_running = Arc::new(AtomicBool::new(true));
rx.try_iter().try_for_each(|ev| {
handle_event(
ev,
&mut out,
&mut ps,
&mut false,
#[cfg(feature = "search")]
&input_thread_running,
)
})?;
Ok(ps)
}