use ratatui::layout::Constraint;
use ratatui::widgets::{Scrollbar, ScrollbarOrientation, Table};
use ratatui::{Frame, layout::Rect, style::Style, widgets::Row};
use super::{BLUE, ORANGE, effective_mode, pane_block};
use crate::{Mode, State};
pub fn draw_mapping(state: &mut State, f: &mut Frame, mapping_rect: Rect) {
let header = Row::new(["Start Address", "End Address", "Size", "Offset", "Perms", "Path"])
.style(Style::new().fg(BLUE).bold());
let mut rows = vec![];
let memory_map = state.memory_map.clone();
if let Some(memory_map) = memory_map.as_ref() {
for (index, m) in memory_map.iter().enumerate() {
let mut row = Row::new([
format!("0x{:08x}", m.start_address),
format!("0x{:08x}", m.end_address),
format!("0x{:08x}", m.size),
format!("0x{:08x}", m.offset),
m.permissions.clone().unwrap_or(String::new()),
m.path.clone().unwrap_or(String::new()),
]);
if index == state.memory_map_selected {
row = row.style(Style::new().fg(ORANGE).bold());
}
rows.push(row);
}
}
let len = rows.len();
let max = mapping_rect.height.saturating_sub(2);
state.memory_map_viewport_height = max;
state.memory_map_scroll.set_max_scroll(len.saturating_sub(max as usize));
let skip = state.memory_map_scroll.scroll;
let rows: Vec<Row> = rows.into_iter().skip(skip).take(max as usize).collect();
let widths = [
Constraint::Length(18),
Constraint::Length(18),
Constraint::Length(12),
Constraint::Length(12),
Constraint::Length(11),
Constraint::Fill(1),
];
let active = matches!(effective_mode(state), Mode::OnlyMapping);
let block = pane_block("Memory Mapping", None, "H hexdump", active);
let table = Table::new(rows, widths).header(header).block(block);
f.render_widget(table, mapping_rect);
f.render_stateful_widget(
Scrollbar::new(ScrollbarOrientation::VerticalRight),
mapping_rect,
&mut state.memory_map_scroll.state,
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mi::MemoryMapping;
use crate::{Args, PtrSize};
use ratatui::Terminal;
use ratatui::backend::TestBackend;
fn create_test_state() -> State {
let args = Args {
gdb_path: None,
remote: None,
ptr_size: PtrSize::Size64,
cmds: None,
log_path: None,
};
State::new(args)
}
fn create_test_mapping() -> MemoryMapping {
MemoryMapping {
start_address: 0x400000,
end_address: 0x401000,
size: 0x1000,
offset: 0x0,
permissions: Some("r-xp".to_string()),
path: Some("/bin/test".to_string()),
}
}
#[test]
fn test_draw_mapping_empty() {
let mut state = create_test_state();
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let area = f.area();
draw_mapping(&mut state, f, area);
})
.unwrap();
assert_eq!(state.memory_map_viewport_height, 22);
}
#[test]
fn test_draw_mapping_with_data() {
let mut state = create_test_state();
state.memory_map = Some(vec![create_test_mapping()]);
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let area = f.area();
draw_mapping(&mut state, f, area);
})
.unwrap();
assert_eq!(state.memory_map_viewport_height, 22);
}
#[test]
fn test_draw_mapping_multiple_entries() {
let mut state = create_test_state();
state.memory_map = Some(vec![
create_test_mapping(),
MemoryMapping {
start_address: 0x500000,
end_address: 0x501000,
size: 0x1000,
offset: 0x0,
permissions: Some("rw-p".to_string()),
path: Some("/lib/test.so".to_string()),
},
MemoryMapping {
start_address: 0x600000,
end_address: 0x602000,
size: 0x2000,
offset: 0x1000,
permissions: None,
path: None,
},
]);
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let area = f.area();
draw_mapping(&mut state, f, area);
})
.unwrap();
assert_eq!(state.memory_map_viewport_height, 22);
}
#[test]
fn test_draw_mapping_with_selection() {
let mut state = create_test_state();
state.memory_map = Some(vec![create_test_mapping(), create_test_mapping()]);
state.memory_map_selected = 1;
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let area = f.area();
draw_mapping(&mut state, f, area);
})
.unwrap();
assert_eq!(state.memory_map_selected, 1);
}
#[test]
fn test_draw_mapping_with_scroll() {
let mut state = create_test_state();
let mappings: Vec<MemoryMapping> = (0..50)
.map(|i| MemoryMapping {
start_address: 0x400000 + (i * 0x1000),
end_address: 0x401000 + (i * 0x1000),
size: 0x1000,
offset: 0x0,
permissions: Some("r-xp".to_string()),
path: Some(format!("/path/to/lib{i}.so")),
})
.collect();
state.memory_map = Some(mappings);
state.memory_map_scroll.scroll = 10;
let backend = TestBackend::new(80, 24);
let mut terminal = Terminal::new(backend).unwrap();
terminal
.draw(|f| {
let area = f.area();
draw_mapping(&mut state, f, area);
})
.unwrap();
assert_eq!(state.memory_map_scroll.scroll, 10);
}
}