#[derive(Debug, Clone, Default)]
pub struct PdbHandle {
pub stack_len: usize,
pub curframe: crate::ported::segments::pdb::PdbCurFrame,
}
pub struct PDBRenderer {
pub pdb: Option<PdbHandle>,
pub initial_stack_length: Option<usize>,
}
impl Default for PDBRenderer {
fn default() -> Self {
Self::new()
}
}
impl PDBRenderer {
#[allow(non_upper_case_globals)]
pub const escape_hl_start: &'static str =
crate::ported::renderers::shell::readline::ReadlineRenderer::escape_hl_start;
#[allow(non_upper_case_globals)]
pub const escape_hl_end: &'static str =
crate::ported::renderers::shell::readline::ReadlineRenderer::escape_hl_end;
pub fn new() -> Self {
Self {
pdb: None, initial_stack_length: None, }
}
pub fn set_pdb(&mut self, pdb: PdbHandle) {
self.pdb = Some(pdb);
}
pub fn get_segment_info(
&self,
segment_info: &serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
let mut r = segment_info.clone();
if let Some(pdb) = &self.pdb {
r.insert(
"pdb".to_string(),
serde_json::json!({"stack_len": pdb.stack_len}),
);
r.insert(
"curframe".to_string(),
serde_json::json!({
"f_lineno": pdb.curframe.f_lineno,
"co_filename": pdb.curframe.co_filename,
"co_name": pdb.curframe.co_name,
}),
);
}
r.insert(
"initial_stack_length".to_string(),
serde_json::json!(self.initial_stack_length.map(|n| n as u64)),
);
r
}
pub fn render(&mut self) -> String {
if self.initial_stack_length.is_none() {
if let Some(pdb) = &self.pdb {
self.initial_stack_length = Some(pdb.stack_len.saturating_sub(1));
}
}
String::new()
}
pub fn do_render<F>(&self, base_do_render: F) -> String
where
F: FnOnce() -> String,
{
let ret = base_do_render();
let folded: String = ret
.chars()
.map(|c| if c.is_ascii() { c } else { '?' })
.collect();
folded
}
}
#[allow(non_camel_case_types)]
pub type renderer = PDBRenderer;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_pdb_renderer_has_no_pdb_set() {
let r = PDBRenderer::new();
assert!(r.pdb.is_none());
assert!(r.initial_stack_length.is_none());
}
#[test]
fn set_pdb_stores_handle() {
let mut r = PDBRenderer::new();
r.set_pdb(PdbHandle {
stack_len: 5,
..Default::default()
});
assert_eq!(r.pdb.as_ref().unwrap().stack_len, 5);
}
#[test]
fn render_initializes_initial_stack_length() {
let mut r = PDBRenderer::new();
r.set_pdb(PdbHandle {
stack_len: 5,
..Default::default()
});
r.render();
assert_eq!(r.initial_stack_length, Some(4));
}
#[test]
fn render_does_not_overwrite_initial_stack_length_on_second_call() {
let mut r = PDBRenderer::new();
r.set_pdb(PdbHandle {
stack_len: 5,
..Default::default()
});
r.render();
r.pdb.as_mut().unwrap().stack_len = 10;
r.render();
assert_eq!(r.initial_stack_length, Some(4));
}
#[test]
fn get_segment_info_inserts_curframe_and_pdb_keys() {
let mut r = PDBRenderer::new();
r.set_pdb(PdbHandle {
stack_len: 3,
curframe: crate::ported::segments::pdb::PdbCurFrame {
f_lineno: 42,
co_filename: "/tmp/x.py".to_string(),
co_name: "foo".to_string(),
},
});
let seg = serde_json::Map::new();
let out = r.get_segment_info(&seg);
assert_eq!(out["curframe"]["f_lineno"], 42);
assert_eq!(out["curframe"]["co_name"], "foo");
assert_eq!(out["pdb"]["stack_len"], 3);
}
#[test]
fn pdb_renderer_inherits_readline_escape_markers() {
assert_eq!(PDBRenderer::escape_hl_start, "\x01");
assert_eq!(PDBRenderer::escape_hl_end, "\x02");
}
#[test]
fn do_render_strips_non_ascii_chars() {
let r = PDBRenderer::new();
let out = r.do_render(|| "hello\u{2603}world".to_string());
assert_eq!(out, "hello?world");
}
#[test]
fn do_render_passes_ascii_unchanged() {
let r = PDBRenderer::new();
let out = r.do_render(|| "plain ASCII string".to_string());
assert_eq!(out, "plain ASCII string");
}
}