use crate::debugger::process::{Child, Installed};
use crate::debugger::{BreakpointViewOwned, Debugger, DebuggerBuilder, WatchpointViewOwned};
use crate::ui::proto::{Request, exchanger};
use crate::ui::tui::app::Model;
pub use crate::ui::tui::app::port::TuiHook;
use crate::ui::tui::app::port::{DebuggerEventQueue, UserEvent};
use crate::ui::tui::components::popup::Popup;
use crate::ui::tui::output::{OutputLine, OutputStreamProcessor, StreamType};
use crate::ui::{DebugeeOutReader, console, supervisor};
use crate::weak_error;
use anyhow::anyhow;
use crossterm::event::{DisableMouseCapture, EnableMouseCapture};
use crossterm::execute;
use log::error;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use std::{io, thread};
use strum_macros::{Display, EnumString};
use timeout_readwrite::TimeoutReader;
use tuirealm::{AttrValue, Attribute, PollStrategy, props};
pub mod app;
pub mod components;
pub mod config;
mod output;
pub mod utils;
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
pub enum Id {
LeftTabs,
RightTabs,
Status,
GlobalControl,
Input,
Popup,
}
#[derive(Debug, PartialEq, EnumString, Display, Clone)]
pub enum ConfirmedAction {
Restart,
RemoveBreakpoint,
RemoveWatchpoint,
}
#[derive(Debug, PartialEq, Clone)]
pub enum BreakpointsAddType {
AtLine,
AtFunction,
AtAddress,
Watchpoint,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Msg {
None,
AppClose,
AppRunning,
LeftTabsInFocus { reset_to: Option<props::Direction> },
RightTabsInFocus { reset_to: Option<props::Direction> },
SwitchUI,
BreakpointAdd(BreakpointsAddType),
UpdateBreakpointList,
ExpandTab(Id),
PopupConfirmDebuggerRestart,
PopupBreakpoint(BreakpointViewOwned),
PopupWatchpoint(WatchpointViewOwned),
ShowOkPopup(Option<String>, String),
PopupOk,
PopupYes(ConfirmedAction),
PopupNo(ConfirmedAction),
Input(String),
InputCancel,
}
#[derive(Default, Clone)]
pub struct DebugeeStreamBuffer {
data: Arc<Mutex<Vec<OutputLine>>>,
}
pub struct AppBuilder {
debugee_out: DebugeeOutReader,
debugee_err: DebugeeOutReader,
}
impl AppBuilder {
pub fn new(debugee_out: DebugeeOutReader, debugee_err: DebugeeOutReader) -> Self {
Self {
debugee_out,
debugee_err,
}
}
pub fn build(
self,
dbg_builder: DebuggerBuilder<TuiHook>,
process: Child<Installed>,
) -> anyhow::Result<TuiApplication> {
let debugger_event_queue = DebuggerEventQueue::default();
let debugger = dbg_builder
.with_hooks(TuiHook::new(debugger_event_queue.clone()))
.build(process)?;
Ok(TuiApplication::new(
debugger,
self.debugee_out,
self.debugee_err,
debugger_event_queue,
))
}
pub fn extend(self, mut debugger: Debugger) -> TuiApplication {
let debugger_event_queue = DebuggerEventQueue::default();
debugger.set_hook(TuiHook::new(debugger_event_queue.clone()));
TuiApplication::new(
debugger,
self.debugee_out,
self.debugee_err,
debugger_event_queue,
)
}
}
pub struct TuiApplication {
debugger: Debugger,
debugee_out: DebugeeOutReader,
debugee_err: DebugeeOutReader,
debugger_event_queue: Arc<Mutex<Vec<UserEvent>>>,
}
impl TuiApplication {
pub fn new(
debugger: Debugger,
debugee_out: DebugeeOutReader,
debugee_err: DebugeeOutReader,
debugger_event_queue: Arc<Mutex<Vec<UserEvent>>>,
) -> Self {
Self {
debugger,
debugee_out,
debugee_err,
debugger_event_queue,
}
}
pub fn run(mut self) -> anyhow::Result<supervisor::ControlFlow> {
let log_buffer = Arc::new(Mutex::default());
let logger = utils::logger::TuiLogger::new(log_buffer.clone());
let filter = logger.filter();
crate::log::LOGGER_SWITCHER.switch(logger, filter);
let stream_buf = DebugeeStreamBuffer::default();
let out = TimeoutReader::new(self.debugee_out.clone(), Duration::from_millis(1));
let std_out_handle =
OutputStreamProcessor::new(StreamType::StdOut).run(out, stream_buf.data.clone());
let out = TimeoutReader::new(self.debugee_err.clone(), Duration::from_millis(1));
let std_err_handle =
OutputStreamProcessor::new(StreamType::StdErr).run(out, stream_buf.data.clone());
let (srv_exchanger, client_exchanger) = exchanger();
let ui_jh = thread::spawn(move || -> anyhow::Result<()> {
let mut model = Model::new(
stream_buf,
self.debugger_event_queue,
client_exchanger,
log_buffer,
)?;
model.terminal.enter_alternate_screen()?;
model.terminal.enable_raw_mode()?;
weak_error!(execute!(io::stdout(), DisableMouseCapture));
while !model.quit {
match model.app.tick(PollStrategy::Once) {
Err(err) => {
model.app.attr(
&Id::Popup,
Attribute::Title,
AttrValue::String("TUI error".to_string()),
)?;
model.app.attr(
&Id::Popup,
Attribute::Text,
AttrValue::String(err.to_string()),
)?;
let (ok_attr, ok_attr_val) = Popup::ok_attrs();
model.app.attr(&Id::Popup, ok_attr, ok_attr_val)?;
model.app.active(&Id::Popup)?;
}
Ok(messages) if !messages.is_empty() => {
model.redraw = true;
for msg in messages.into_iter() {
let mut msg = Some(msg);
while msg.is_some() {
msg = model.update(msg).unwrap_or_else(|e| {
Some(Msg::ShowOkPopup(Some("Error".to_string()), e.to_string()))
});
}
}
}
_ => {}
}
if model.redraw {
model.view();
model.redraw = false;
}
}
weak_error!(execute!(io::stdout(), EnableMouseCapture));
model.terminal.leave_alternate_screen()?;
model.terminal.disable_raw_mode()?;
model.terminal.clear_screen()?;
Ok(())
});
enum ExitType {
Exit,
Shutdown,
SwitchUi,
}
let exit_type;
loop {
match srv_exchanger.next_request() {
Some(Request::Exit) | Some(Request::ExitSync) => {
exit_type = ExitType::Exit;
break;
}
Some(Request::SwitchUi) => {
exit_type = ExitType::SwitchUi;
break;
}
Some(Request::DebuggerSyncTask(task)) => {
let result = task(&mut self.debugger);
srv_exchanger.send_response(result);
}
Some(Request::DebuggerAsyncTask(task)) => {
if let Err(e) = task(&mut self.debugger) {
srv_exchanger.send_async_response(e);
}
}
None => {
exit_type = ExitType::Shutdown;
break;
}
}
}
drop(std_out_handle);
drop(std_err_handle);
match exit_type {
ExitType::Exit => Ok(supervisor::ControlFlow::Exit),
ExitType::Shutdown => {
let join_result = ui_jh.join();
let join_result = join_result
.map_err(|_| anyhow!("unexpected: tui thread panic"))
.and_then(|r| r);
if let Err(e) = join_result {
error!(target: "tui", "tui thread error: {e}");
};
Ok(supervisor::ControlFlow::Exit)
}
ExitType::SwitchUi => {
_ = ui_jh.join();
let builder = console::AppBuilder::new(self.debugee_out, self.debugee_err);
let app = builder
.extend(self.debugger)
.expect("build application fail");
Ok(supervisor::ControlFlow::Switch(
supervisor::Application::Terminal(app),
))
}
}
}
}