#[cfg(feature = "static_output")]
use crate::minus_core::utils::display;
use crate::{
error::MinusError,
input::InputEvent,
minus_core::{
commands::Command,
ev_handler::handle_event,
utils::{display::draw_full, term},
RunMode,
},
Pager, PagerState,
};
use crossbeam_channel::{Receiver, Sender, TrySendError};
use crossterm::event;
use std::{
io::{stdout, Stdout},
panic,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
#[cfg(feature = "static_output")]
use {super::utils::display::write_raw_lines, crossterm::tty::IsTty};
#[cfg(feature = "search")]
use parking_lot::Condvar;
use parking_lot::Mutex;
use super::{utils::display::draw_for_change, CommandQueue, RUNMODE};
#[allow(clippy::module_name_repetitions)]
#[allow(clippy::too_many_lines)]
pub fn init_core(pager: &Pager, rm: RunMode) -> std::result::Result<(), MinusError> {
#[allow(unused_mut)]
let mut out = stdout();
#[cfg(feature = "search")]
let input_thread_running = Arc::new((Mutex::new(true), Condvar::new()));
#[allow(unused_mut)]
let mut ps = crate::state::PagerState::generate_initial_state(&pager.rx, &mut out)?;
{
let mut runmode = super::RUNMODE.lock();
assert!(runmode.is_uninitialized(), "Failed to set the RUNMODE. This is caused probably because another instance of minus is already running");
*runmode = rm;
drop(runmode);
}
#[cfg(feature = "static_output")]
if *RUNMODE.lock() == RunMode::Static {
if !out.is_tty() {
write_raw_lines(&mut out, &[ps.screen.orig_text], None)?;
let mut rm = RUNMODE.lock();
*rm = RunMode::Uninitialized;
drop(rm);
return Ok(());
}
if ps.screen.formatted_lines_count() <= ps.rows && !ps.run_no_overflow {
write_raw_lines(&mut out, &ps.screen.formatted_lines, Some("\r"))?;
ps.exit();
let mut rm = RUNMODE.lock();
*rm = RunMode::Uninitialized;
drop(rm);
return Ok(());
}
}
term::setup(&out)?;
let is_exited = Arc::new(AtomicBool::new(false));
let is_exited2 = is_exited.clone();
{
let panic_hook = panic::take_hook();
panic::set_hook(Box::new(move |pinfo| {
is_exited2.store(true, std::sync::atomic::Ordering::SeqCst);
drop(term::cleanup(
stdout(),
&crate::ExitStrategy::PagerQuit,
true,
));
panic_hook(pinfo);
}));
}
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();
std::thread::scope(|s| -> crate::Result {
let out = Arc::new(out);
let out_copy = out.clone();
let is_exited3 = is_exited.clone();
let is_exited4 = is_exited.clone();
let t1 = s.spawn(move || {
let res = event_reader(
&evtx,
&p1,
#[cfg(feature = "search")]
&input_thread_running2,
&is_exited3,
);
if res.is_err() {
is_exited3.store(true, std::sync::atomic::Ordering::SeqCst);
let mut rm = RUNMODE.lock();
*rm = RunMode::Uninitialized;
drop(rm);
term::cleanup(out.as_ref(), &crate::ExitStrategy::PagerQuit, true)?;
}
res
});
let t2 = s.spawn(move || {
let res = start_reactor(
&rx,
&ps_mutex,
&out_copy,
#[cfg(feature = "search")]
&input_thread_running,
&is_exited4,
);
if res.is_err() {
is_exited4.store(true, std::sync::atomic::Ordering::SeqCst);
let mut rm = RUNMODE.lock();
*rm = RunMode::Uninitialized;
drop(rm);
term::cleanup(out_copy.as_ref(), &crate::ExitStrategy::PagerQuit, true)?;
}
res
});
let r1 = t1.join().unwrap();
let r2 = t2.join().unwrap();
r1?;
r2?;
Ok(())
})
}
#[allow(clippy::too_many_lines)]
fn start_reactor(
rx: &Receiver<Command>,
ps: &Arc<Mutex<PagerState>>,
out: &Stdout,
#[cfg(feature = "search")] input_thread_running: &Arc<(Mutex<bool>, Condvar)>,
is_exited: &Arc<AtomicBool>,
) -> Result<(), MinusError> {
let mut out_lock = out.lock();
let mut command_queue = CommandQueue::new();
{
let mut p = ps.lock();
draw_full(&mut out_lock, &mut p)?;
if p.follow_output {
draw_for_change(&mut out_lock, &mut p, &mut (usize::MAX - 1))?;
}
}
let run_mode = *RUNMODE.lock();
match run_mode {
#[cfg(feature = "dynamic_output")]
RunMode::Dynamic => loop {
if is_exited.load(Ordering::SeqCst) {
let mut rm = RUNMODE.lock();
*rm = RunMode::Uninitialized;
drop(rm);
break;
}
let next_command = if command_queue.is_empty() {
rx.recv()
} else {
Ok(command_queue.pop_front().unwrap())
};
if let Ok(command) = next_command {
let mut p = ps.lock();
handle_event(
command,
&mut out_lock,
&mut p,
&mut command_queue,
is_exited,
#[cfg(feature = "search")]
input_thread_running,
)?;
}
},
#[cfg(feature = "static_output")]
RunMode::Static => {
{
let mut p = ps.lock();
if p.follow_output {
display::draw_for_change(&mut out_lock, &mut p, &mut (usize::MAX - 1))?;
}
}
loop {
if is_exited.load(Ordering::SeqCst) {
term::cleanup(&mut out_lock, &ps.lock().exit_strategy, true)?;
let mut rm = RUNMODE.lock();
*rm = RunMode::Uninitialized;
drop(rm);
break;
}
let next_command = if command_queue.is_empty() {
rx.recv()
} else {
Ok(command_queue.pop_front().unwrap())
};
if let Ok(command) = next_command {
let mut p = ps.lock();
handle_event(
command,
&mut out_lock,
&mut p,
&mut command_queue,
is_exited,
#[cfg(feature = "search")]
input_thread_running,
)?;
}
}
}
RunMode::Uninitialized => panic!(
"Static variable RUNMODE set to uninitialized.\
This is most likely a bug. Please open an issue to the developers"
),
}
Ok(())
}
fn event_reader(
evtx: &Sender<Command>,
ps: &Arc<Mutex<PagerState>>,
#[cfg(feature = "search")] user_input_active: &Arc<(Mutex<bool>, Condvar)>,
is_exited: &Arc<AtomicBool>,
) -> Result<(), MinusError> {
loop {
if is_exited.load(Ordering::SeqCst) {
break;
}
#[cfg(feature = "search")]
{
let (lock, cvar) = (&user_input_active.0, &user_input_active.1);
let mut active = lock.lock();
if !*active {
cvar.wait(&mut active);
}
}
if event::poll(std::time::Duration::from_millis(100))
.map_err(|e| MinusError::HandleEvent(e.into()))?
{
let ev = event::read().map_err(|e| MinusError::HandleEvent(e.into()))?;
let mut guard = ps.lock();
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);
guard.format_prompt();
} else if !guard.prefix_num.is_empty() {
guard.prefix_num.clear();
guard.format_prompt();
}
if let Err(TrySendError::Disconnected(_)) = evtx.try_send(Command::UserInput(iev)) {
break;
}
} else if !guard.prefix_num.is_empty() {
guard.prefix_num.clear();
guard.format_prompt();
}
}
}
Result::<(), MinusError>::Ok(())
}