use crate::debugger::address::RelocatedAddress;
use crate::debugger::register::debug::BreakCondition;
use crate::debugger::variable::value::Value;
use crate::debugger::{EventHook, FunctionInfo, PlaceDescriptor};
use crate::ui::proto::ClientExchanger;
use crate::ui::tui::output::OutputLine;
use crate::ui::tui::utils::logger::TuiLogLine;
use crate::version;
use log::warn;
use nix::sys::signal::Signal;
use nix::unistd::Pid;
use std::cmp::Ordering;
use std::sync::{Arc, Mutex};
use tuirealm::Event;
use tuirealm::listener::{ListenerResult, Poll};
impl PartialOrd for Value {
fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
None
}
}
#[derive(Clone, PartialOrd)]
#[allow(clippy::large_enum_variant)]
pub enum UserEvent {
GotOutput(Vec<OutputLine>, usize),
Breakpoint {
pc: RelocatedAddress,
num: u32,
file: Option<String>,
line: Option<u64>,
function: Option<String>,
},
Watchpoint {
pc: RelocatedAddress,
num: u32,
file: Option<String>,
line: Option<u64>,
cond: BreakCondition,
old_value: Option<Value>,
new_value: Option<Value>,
end_of_scope: bool,
},
Step {
pc: RelocatedAddress,
file: Option<String>,
line: Option<u64>,
function: Option<String>,
},
Signal(Signal),
Exit(i32),
AsyncErrorResponse(String),
Logs(Vec<TuiLogLine>),
ProcessInstall(Pid),
}
impl PartialEq for UserEvent {
fn eq(&self, other: &Self) -> bool {
match self {
UserEvent::GotOutput(_, _) => matches!(other, UserEvent::GotOutput(_, _)),
UserEvent::Breakpoint { .. } => matches!(other, UserEvent::Breakpoint { .. }),
UserEvent::Step { .. } => {
matches!(other, UserEvent::Step { .. })
}
UserEvent::Signal(_) => {
matches!(other, UserEvent::Signal(_))
}
UserEvent::Exit(_) => {
matches!(other, UserEvent::Exit(_))
}
UserEvent::AsyncErrorResponse(_) => {
matches!(other, UserEvent::AsyncErrorResponse(_))
}
UserEvent::Logs(_) => {
matches!(other, UserEvent::Logs(_))
}
UserEvent::ProcessInstall(_) => {
matches!(other, UserEvent::ProcessInstall(_))
}
UserEvent::Watchpoint { .. } => matches!(other, UserEvent::Watchpoint { .. }),
}
}
}
impl Eq for UserEvent {}
pub struct OutputPort {
output_buf: Arc<Mutex<Vec<OutputLine>>>,
read_line_count: usize,
}
impl OutputPort {
pub fn new(out_buf: Arc<Mutex<Vec<OutputLine>>>) -> Self {
Self {
output_buf: out_buf,
read_line_count: 0,
}
}
}
impl Poll<UserEvent> for OutputPort {
fn poll(&mut self) -> ListenerResult<Option<Event<UserEvent>>> {
let lock = self.output_buf.lock().unwrap();
if lock.len() != self.read_line_count {
let event = UserEvent::GotOutput(lock.clone(), lock.len() - self.read_line_count);
self.read_line_count = lock.len();
return Ok(Some(Event::User(event)));
}
Ok(None)
}
}
pub type DebuggerEventQueue = Arc<Mutex<Vec<UserEvent>>>;
pub struct TuiHook {
event_queue: DebuggerEventQueue,
}
impl TuiHook {
pub fn new(event_queue: DebuggerEventQueue) -> Self {
Self { event_queue }
}
}
impl EventHook for TuiHook {
fn on_breakpoint(
&self,
pc: RelocatedAddress,
num: u32,
place: Option<PlaceDescriptor>,
function: Option<&FunctionInfo>,
_: Option<u32>,
) -> anyhow::Result<()> {
self.event_queue
.lock()
.unwrap()
.push(UserEvent::Breakpoint {
pc,
num,
file: place.as_ref().map(|p| p.file.to_string_lossy().to_string()),
line: place.as_ref().map(|p| p.line_number),
function: function.and_then(|f| f.name.clone()),
});
Ok(())
}
fn on_watchpoint(
&self,
pc: RelocatedAddress,
num: u32,
place: Option<PlaceDescriptor>,
cond: BreakCondition,
_: Option<&str>,
old: Option<&Value>,
new: Option<&Value>,
end_of_scope: bool,
) -> anyhow::Result<()> {
self.event_queue
.lock()
.unwrap()
.push(UserEvent::Watchpoint {
pc,
num,
file: place.as_ref().map(|p| p.file.to_string_lossy().to_string()),
line: place.as_ref().map(|p| p.line_number),
cond,
old_value: old.cloned(),
new_value: new.cloned(),
end_of_scope,
});
Ok(())
}
fn on_step(
&self,
pc: RelocatedAddress,
place: Option<PlaceDescriptor>,
function: Option<&FunctionInfo>,
_: Option<u32>,
) -> anyhow::Result<()> {
self.event_queue.lock().unwrap().push(UserEvent::Step {
pc,
file: place.as_ref().map(|p| p.file.to_string_lossy().to_string()),
line: place.as_ref().map(|p| p.line_number),
function: function.and_then(|f| f.name.clone()),
});
Ok(())
}
fn on_async_step(
&self,
pc: RelocatedAddress,
place: Option<PlaceDescriptor>,
function: Option<&FunctionInfo>,
_: u64,
_: bool,
) -> anyhow::Result<()> {
self.on_step(pc, place, function, None)
}
fn on_signal(&self, signal: Signal) {
self.event_queue
.lock()
.unwrap()
.push(UserEvent::Signal(signal));
}
fn on_exit(&self, code: i32) {
self.event_queue.lock().unwrap().push(UserEvent::Exit(code));
}
fn on_process_install(&self, pid: Pid, object: Option<&object::File>) {
if let Some(obj) = object
&& !version::probe_file(obj)
{
let supported_versions = version::supported_versions_to_string();
warn!(target: "debugger", "Found unsupported rust version, some of program data may not be displayed correctly. \
List of supported rustc versions: {supported_versions}.");
}
self.event_queue
.lock()
.unwrap()
.push(UserEvent::ProcessInstall(pid));
}
}
pub struct DebuggerEventsPort {
event_queue: DebuggerEventQueue,
}
impl DebuggerEventsPort {
pub fn new(event_queue: DebuggerEventQueue) -> Self {
Self { event_queue }
}
}
impl Poll<UserEvent> for DebuggerEventsPort {
fn poll(&mut self) -> ListenerResult<Option<Event<UserEvent>>> {
if let Some(event) = self.event_queue.lock().unwrap().pop() {
return Ok(Some(Event::User(event)));
}
Ok(None)
}
}
pub struct AsyncResponsesPort {
exchanger: Arc<ClientExchanger>,
}
impl AsyncResponsesPort {
pub fn new(exchanger: Arc<ClientExchanger>) -> Self {
Self { exchanger }
}
}
impl Poll<UserEvent> for AsyncResponsesPort {
fn poll(&mut self) -> ListenerResult<Option<Event<UserEvent>>> {
Ok(self
.exchanger
.poll_async_resp()
.map(|err| Event::User(UserEvent::AsyncErrorResponse(format!("{err:#}")))))
}
}
pub struct LoggerPort {
buffer: Arc<Mutex<Vec<TuiLogLine>>>,
}
impl LoggerPort {
pub fn new(buffer: Arc<Mutex<Vec<TuiLogLine>>>) -> Self {
Self { buffer }
}
}
impl Poll<UserEvent> for LoggerPort {
fn poll(&mut self) -> ListenerResult<Option<Event<UserEvent>>> {
let mut buffer = self.buffer.lock().unwrap();
let logs = buffer.clone();
buffer.clear();
if logs.is_empty() {
Ok(None)
} else {
Ok(Some(Event::User(UserEvent::Logs(logs))))
}
}
}