use crate::buf::{BuffersManager, BuffersManagerArc};
use crate::cli::CliOpt;
use crate::envar;
use crate::evloop::msg::WorkerToMasterMessage;
use crate::js::msg::{self as jsmsg, EventLoopToJsRuntimeMessage, JsRuntimeToEventLoopMessage};
use crate::js::{JsRuntime, JsRuntimeOptions, SnapshotData};
use crate::lock;
use crate::prelude::*;
use crate::state::fsm::{StatefulDataAccess, StatefulValue, StatefulValueArc};
use crate::state::{State, StateArc};
use crate::ui::canvas::{Canvas, CanvasArc, Shader, ShaderCommand};
use crate::ui::tree::*;
use crate::ui::widget::cursor::Cursor;
use crate::ui::widget::window::Window;
use crossterm::event::{Event, EventStream};
use crossterm::{self, queue};
use futures::StreamExt;
use parking_lot::Mutex;
use std::path::{Path, PathBuf};
use std::time::{Instant, SystemTime, UNIX_EPOCH};
use std::io::Write;
use std::io::{BufWriter, Stdout};
use std::sync::Arc;
use tokio::sync::mpsc::{Receiver, Sender, channel};
use tokio_util::sync::CancellationToken;
use tokio_util::task::TaskTracker;
use tracing::{error, trace};
pub mod msg;
pub mod task;
pub mod tui;
pub struct EventLoop {
pub startup_moment: Instant,
pub startup_unix_epoch: u128,
pub cli_opt: CliOpt,
pub runtime_path: Arc<Mutex<Vec<PathBuf>>>,
pub tree: TreeArc,
pub canvas: CanvasArc,
pub writer: BufWriter<Stdout>,
pub state: StateArc,
pub stateful_machine: StatefulValueArc,
pub buffers: BuffersManagerArc,
pub cancellation_token: CancellationToken,
pub detached_tracker: TaskTracker,
pub blocked_tracker: TaskTracker,
pub worker_send_to_master: Sender<WorkerToMasterMessage>,
pub master_recv_from_worker: Receiver<WorkerToMasterMessage>,
pub js_runtime: JsRuntime,
pub master_recv_from_js_runtime: Receiver<JsRuntimeToEventLoopMessage>,
pub master_send_to_js_runtime: Sender<EventLoopToJsRuntimeMessage>,
pub js_runtime_tick_dispatcher: Sender<EventLoopToJsRuntimeMessage>,
pub js_runtime_tick_queue: Receiver<EventLoopToJsRuntimeMessage>,
}
impl EventLoop {
pub fn new(cli_opt: CliOpt, snapshot: SnapshotData) -> IoResult<Self> {
let (cols, rows) = crossterm::terminal::size()?;
let canvas_size = U16Size::new(cols, rows);
let canvas = Canvas::new(canvas_size);
let canvas = Canvas::to_arc(canvas);
let tree = Tree::to_arc(Tree::new(canvas_size));
let buffers_manager = BuffersManager::to_arc(BuffersManager::new());
let state = State::to_arc(State::default());
let stateful_machine = StatefulValue::to_arc(StatefulValue::default());
let (worker_send_to_master, master_recv_from_worker) = channel(envar::CHANNEL_BUF_SIZE());
let (js_runtime_send_to_master, master_recv_from_js_runtime) =
channel(envar::CHANNEL_BUF_SIZE());
let (master_send_to_js_runtime, js_runtime_recv_from_master) =
channel(envar::CHANNEL_BUF_SIZE());
let (js_runtime_tick_dispatcher, js_runtime_tick_queue) = channel(envar::CHANNEL_BUF_SIZE());
let runtime_path = envar::CONFIG_DIRS_PATH();
let runtime_path = Arc::new(Mutex::new(runtime_path));
let detached_tracker = TaskTracker::new();
let blocked_tracker = TaskTracker::new();
let startup_moment = Instant::now();
let startup_unix_epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
let js_runtime = JsRuntime::new(
JsRuntimeOptions::default(),
snapshot,
startup_moment,
startup_unix_epoch,
js_runtime_send_to_master,
js_runtime_recv_from_master,
cli_opt.clone(),
runtime_path.clone(),
tree.clone(),
buffers_manager.clone(),
state.clone(),
);
Ok(EventLoop {
startup_moment,
startup_unix_epoch,
cli_opt,
runtime_path,
canvas,
tree,
state,
stateful_machine,
buffers: buffers_manager,
writer: BufWriter::new(std::io::stdout()),
cancellation_token: CancellationToken::new(),
detached_tracker,
blocked_tracker,
worker_send_to_master,
master_recv_from_worker,
js_runtime,
master_recv_from_js_runtime,
master_send_to_js_runtime,
js_runtime_tick_dispatcher,
js_runtime_tick_queue,
})
}
pub fn init_config(&mut self) -> IoResult<()> {
if let Some(config_file) = envar::CONFIG_FILE_PATH() {
self
.js_runtime
.execute_module(config_file.to_str().unwrap(), None)
.unwrap();
}
Ok(())
}
pub fn init_tui(&self) -> IoResult<()> {
tui::initialize_raw_mode()?;
tui::shutdown_raw_mode_on_panic();
Ok(())
}
pub fn init_tui_complete(&mut self) -> IoResult<()> {
let cursor = {
let canvas = lock!(self.canvas);
*canvas.frame().cursor()
};
if cursor.blinking() {
queue!(self.writer, crossterm::cursor::EnableBlinking)?;
} else {
queue!(self.writer, crossterm::cursor::DisableBlinking)?;
}
if cursor.hidden() {
queue!(self.writer, crossterm::cursor::Hide)?;
} else {
queue!(self.writer, crossterm::cursor::Show)?;
}
queue!(self.writer, cursor.style())?;
queue!(
self.writer,
crossterm::cursor::MoveTo(cursor.pos().x(), cursor.pos().y())
)?;
self.render()?;
Ok(())
}
pub fn shutdown_tui(&self) -> IoResult<()> {
tui::shutdown_raw_mode()
}
pub fn init_buffers(&mut self) -> IoResult<()> {
let canvas_size = lock!(self.canvas).size();
let input_files = self.cli_opt.file().to_vec();
if !input_files.is_empty() {
for input_file in input_files.iter() {
let maybe_buf_id =
lock!(self.buffers).new_file_buffer(canvas_size.height(), Path::new(input_file));
match maybe_buf_id {
Ok(buf_id) => {
trace!("Created file buffer {:?}:{:?}", input_file, buf_id);
}
Err(e) => {
error!("Failed to create file buffer {:?}:{:?}", input_file, e);
}
}
}
} else {
let buf_id = lock!(self.buffers).new_empty_buffer(canvas_size.height());
trace!("Created empty buffer {:?}", buf_id);
}
Ok(())
}
pub fn init_windows(&mut self) -> IoResult<()> {
let (canvas_size, canvas_cursor) = {
let canvas = lock!(self.canvas);
let canvas_size = canvas.size();
let canvas_cursor = *canvas.frame().cursor();
(canvas_size, canvas_cursor)
};
let mut tree = lock!(self.tree);
let tree_root_id = tree.root_id();
let window_shape = IRect::new(
(0, 0),
(canvas_size.width() as isize, canvas_size.height() as isize),
);
let window = {
let buffers = lock!(self.buffers);
let (buf_id, buf) = buffers.first_key_value().unwrap();
trace!("Bind first buffer to default window {:?}", buf_id);
Window::new(
window_shape,
Arc::downgrade(buf),
tree.global_local_options(),
)
};
let window_id = window.id();
let window_node = TreeNode::Window(window);
tree.bounded_insert(tree_root_id, window_node);
let cursor_shape = IRect::new((0, 0), (1, 1));
let cursor = Cursor::new(
cursor_shape,
canvas_cursor.blinking(),
canvas_cursor.hidden(),
canvas_cursor.style(),
);
let cursor_node = TreeNode::Cursor(cursor);
tree.bounded_insert(window_id, cursor_node);
Ok(())
}
async fn process_event(&mut self, event: Option<IoResult<Event>>) {
match event {
Some(Ok(event)) => {
trace!("Polled terminal event ok: {:?}", event);
let data_access = StatefulDataAccess::new(
self.state.clone(),
self.tree.clone(),
self.buffers.clone(),
event,
);
let next_stateful = self.stateful_machine.clone().handle(data_access);
let next_stateful = StatefulValue::to_arc(next_stateful);
{
let mut state = lock!(self.state);
state.update_state_machine(&next_stateful);
}
self.stateful_machine = next_stateful.clone();
if let StatefulValue::QuitState(_) = *next_stateful {
self.cancellation_token.cancel();
}
}
Some(Err(e)) => {
error!("Polled terminal event error: {:?}", e);
self.cancellation_token.cancel();
}
None => {
error!("Terminal event stream is exhausted, exit loop");
self.cancellation_token.cancel();
}
}
}
async fn process_worker_notify(&mut self, msg: Option<WorkerToMasterMessage>) {
trace!("Received {:?} message from workers", msg);
}
async fn process_js_runtime_request(&mut self, msg: Option<JsRuntimeToEventLoopMessage>) {
if let Some(msg) = msg {
match msg {
JsRuntimeToEventLoopMessage::TimeoutReq(req) => {
trace!("process_js_runtime_request timeout_req:{:?}", req.future_id);
let js_runtime_tick_dispatcher = self.js_runtime_tick_dispatcher.clone();
self.detached_tracker.spawn(async move {
tokio::time::sleep(req.duration).await;
let _ = js_runtime_tick_dispatcher
.send(EventLoopToJsRuntimeMessage::TimeoutResp(
jsmsg::TimeoutResp::new(req.future_id, req.duration),
))
.await;
trace!(
"process_js_runtime_request timeout_req:{:?} - done",
req.future_id
);
});
}
}
}
}
async fn process_js_runtime_response(&mut self, msg: Option<EventLoopToJsRuntimeMessage>) {
if let Some(msg) = msg {
trace!("process_js_runtime_response msg:{:?}", msg);
let _ = self.master_send_to_js_runtime.send(msg).await;
self.js_runtime.tick_event_loop();
}
}
async fn process_cancellation_notify(&mut self) {
trace!("Receive cancellation token, exit loop");
self.detached_tracker.close();
self.blocked_tracker.close();
self.blocked_tracker.wait().await;
}
pub async fn run(&mut self) -> IoResult<()> {
let mut reader = EventStream::new();
loop {
tokio::select! {
event = reader.next() => {
self.process_event(event).await;
}
worker_msg = self.master_recv_from_worker.recv() => {
self.process_worker_notify(worker_msg).await;
}
js_req = self.master_recv_from_js_runtime.recv() => {
self.process_js_runtime_request(js_req).await;
}
js_resp = self.js_runtime_tick_queue.recv() => {
self.process_js_runtime_response(js_resp).await;
}
_ = self.cancellation_token.cancelled() => {
self.process_cancellation_notify().await;
break;
}
}
self.render()?;
}
Ok(())
}
fn render(&mut self) -> IoResult<()> {
lock!(self.tree).draw(self.canvas.clone());
let shader = lock!(self.canvas).shade();
self.queue_shader(shader)?;
self.writer.flush()?;
Ok(())
}
fn queue_shader(&mut self, shader: Shader) -> IoResult<()> {
for shader_command in shader.iter() {
match shader_command {
ShaderCommand::CursorSetCursorStyle(command) => queue!(self.writer, command)?,
ShaderCommand::CursorDisableBlinking(command) => queue!(self.writer, command)?,
ShaderCommand::CursorEnableBlinking(command) => queue!(self.writer, command)?,
ShaderCommand::CursorHide(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveDown(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveLeft(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveRight(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveTo(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveToColumn(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveToNextLine(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveToPreviousLine(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveToRow(command) => queue!(self.writer, command)?,
ShaderCommand::CursorMoveUp(command) => queue!(self.writer, command)?,
ShaderCommand::CursorRestorePosition(command) => queue!(self.writer, command)?,
ShaderCommand::CursorSavePosition(command) => queue!(self.writer, command)?,
ShaderCommand::CursorShow(command) => queue!(self.writer, command)?,
ShaderCommand::EventDisableBracketedPaste(command) => queue!(self.writer, command)?,
ShaderCommand::EventDisableFocusChange(command) => queue!(self.writer, command)?,
ShaderCommand::EventDisableMouseCapture(command) => queue!(self.writer, command)?,
ShaderCommand::EventEnableBracketedPaste(command) => queue!(self.writer, command)?,
ShaderCommand::EventEnableFocusChange(command) => queue!(self.writer, command)?,
ShaderCommand::EventEnableMouseCapture(command) => queue!(self.writer, command)?,
ShaderCommand::EventPopKeyboardEnhancementFlags(command) => queue!(self.writer, command)?,
ShaderCommand::EventPushKeyboardEnhancementFlags(command) => queue!(self.writer, command)?,
ShaderCommand::StyleResetColor(command) => queue!(self.writer, command)?,
ShaderCommand::StyleSetAttribute(command) => queue!(self.writer, command)?,
ShaderCommand::StyleSetAttributes(command) => queue!(self.writer, command)?,
ShaderCommand::StyleSetBackgroundColor(command) => queue!(self.writer, command)?,
ShaderCommand::StyleSetColors(command) => queue!(self.writer, command)?,
ShaderCommand::StyleSetForegroundColor(command) => queue!(self.writer, command)?,
ShaderCommand::StyleSetStyle(command) => queue!(self.writer, command)?,
ShaderCommand::StyleSetUnderlineColor(command) => queue!(self.writer, command)?,
ShaderCommand::StylePrintStyledContentString(command) => queue!(self.writer, command)?,
ShaderCommand::StylePrintString(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalBeginSynchronizedUpdate(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalClear(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalDisableLineWrap(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalEnableLineWrap(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalEndSynchronizedUpdate(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalEnterAlternateScreen(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalLeaveAlternateScreen(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalScrollDown(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalScrollUp(command) => queue!(self.writer, command)?,
ShaderCommand::TerminalSetSize(command) => queue!(self.writer, command)?,
}
}
Ok(())
}
}