use crate::error::Result;
use crate::server::channel::Channel;
use crate::server::channel_owner::{
ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
};
use crate::server::connection::ConnectionLike;
use parking_lot::Mutex;
use serde_json::Value;
use std::any::Any;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
type PausedHandlerFuture = Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
type PausedHandler =
Arc<dyn Fn(Option<PausedDetails>) -> PausedHandlerFuture + Send + Sync + 'static>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PausedDetails {
pub location: PausedLocation,
pub title: String,
pub stack: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PausedLocation {
pub file: String,
pub line: Option<i64>,
pub column: Option<i64>,
}
#[derive(Clone)]
pub struct Debugger {
base: ChannelOwnerImpl,
paused_details: Arc<Mutex<Option<PausedDetails>>>,
paused_handlers: Arc<Mutex<Vec<PausedHandler>>>,
}
impl Debugger {
pub fn new(
parent: ParentOrConnection,
type_name: String,
guid: Arc<str>,
initializer: Value,
) -> Result<Self> {
Ok(Self {
base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
paused_details: Arc::new(Mutex::new(None)),
paused_handlers: Arc::new(Mutex::new(Vec::new())),
})
}
#[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
pub async fn request_pause(&self) -> Result<()> {
self.channel()
.send_no_result("requestPause", serde_json::json!({}))
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
pub async fn resume(&self) -> Result<()> {
self.channel()
.send_no_result("resume", serde_json::json!({}))
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
pub async fn next(&self) -> Result<()> {
self.channel()
.send_no_result("next", serde_json::json!({}))
.await
}
#[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
pub async fn run_to(&self, location: PausedLocation) -> Result<()> {
let mut loc = serde_json::json!({ "file": location.file });
if let Some(line) = location.line {
loc["line"] = serde_json::json!(line);
}
if let Some(column) = location.column {
loc["column"] = serde_json::json!(column);
}
self.channel()
.send_no_result("runTo", serde_json::json!({ "location": loc }))
.await
}
pub fn paused_details(&self) -> Option<PausedDetails> {
self.paused_details.lock().clone()
}
pub fn is_paused(&self) -> bool {
self.paused_details.lock().is_some()
}
pub fn on_paused_state_changed<F, Fut>(&self, handler: F)
where
F: Fn(Option<PausedDetails>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
let h: PausedHandler = Arc::new(move |d| -> PausedHandlerFuture { Box::pin(handler(d)) });
self.paused_handlers.lock().push(h);
}
}
fn parse_paused_details(params: &Value) -> Option<PausedDetails> {
let pd = params.get("pausedDetails")?;
if pd.is_null() {
return None;
}
let location = pd.get("location")?;
let file = location.get("file")?.as_str()?.to_string();
let line = location.get("line").and_then(|v| v.as_i64());
let column = location.get("column").and_then(|v| v.as_i64());
let title = pd.get("title")?.as_str()?.to_string();
let stack = pd.get("stack").and_then(|v| v.as_str()).map(String::from);
Some(PausedDetails {
location: PausedLocation { file, line, column },
title,
stack,
})
}
impl ChannelOwner for Debugger {
fn guid(&self) -> &str {
self.base.guid()
}
fn type_name(&self) -> &str {
self.base.type_name()
}
fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
self.base.parent()
}
fn connection(&self) -> Arc<dyn ConnectionLike> {
self.base.connection()
}
fn initializer(&self) -> &Value {
self.base.initializer()
}
fn channel(&self) -> &Channel {
self.base.channel()
}
fn dispose(&self, reason: DisposeReason) {
self.base.dispose(reason)
}
fn adopt(&self, child: Arc<dyn ChannelOwner>) {
self.base.adopt(child)
}
fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
self.base.add_child(guid, child)
}
fn remove_child(&self, guid: &str) {
self.base.remove_child(guid)
}
fn on_event(&self, method: &str, params: Value) {
if method == "pausedStateChanged" {
let details = parse_paused_details(¶ms);
*self.paused_details.lock() = details.clone();
let handlers = self.paused_handlers.lock().clone();
for h in handlers {
let d = details.clone();
tokio::spawn(async move {
if let Err(e) = h(d).await {
tracing::warn!("Debugger paused-state handler error: {}", e);
}
});
}
}
self.base.on_event(method, params);
}
fn was_collected(&self) -> bool {
self.base.was_collected()
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl std::fmt::Debug for Debugger {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Debugger")
.field("guid", &self.guid())
.field("paused", &self.is_paused())
.finish()
}
}