use ratatui::Frame;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use crate::app::App;
use crate::layout::PaneId;
use crate::pane::{DapReplPane, Pane};
use crate::ui::theme;
pub fn draw(frame: &mut Frame, app: &App, pane_id: PaneId, area: Rect, focused: bool) {
let Some(Pane::DapRepl(p)) = app.panes.get(pane_id) else {
return;
};
let t = theme::cur();
let chunks = Layout::vertical([
Constraint::Length(1),
Constraint::Min(1),
Constraint::Length(1),
])
.split(area);
draw_header(frame, p, chunks[0]);
draw_history(frame, app, p, chunks[1]);
draw_input(frame, p, chunks[2], focused);
let _ = t;
}
fn draw_header(frame: &mut Frame, p: &DapReplPane, area: Rect) {
let t = theme::cur();
let line = if p.filter_mode {
Line::from(vec![
Span::styled(" DAP REPL ", Style::default().fg(t.bg_dark).bg(t.cyan)),
Span::styled(
format!(
" filter: {}_ · Backspace · Enter applies · Esc clears",
p.filter
),
Style::default().fg(t.yellow).bg(t.bg_dark),
),
])
} else if !p.filter.is_empty() {
let visible = p.visible_history_indices().len();
Line::from(vec![
Span::styled(" DAP REPL ", Style::default().fg(t.bg_dark).bg(t.cyan)),
Span::styled(
format!(
" ({}/{} match \"{}\") ",
visible,
p.history.len(),
p.filter
),
Style::default().fg(t.yellow).bg(t.bg_dark),
),
Span::styled(
"Enter: eval · ↑↓: history · /: refilter · Esc clears filter",
Style::default().fg(t.comment).bg(t.bg_dark),
),
])
} else {
Line::from(vec![
Span::styled(" DAP REPL ", Style::default().fg(t.bg_dark).bg(t.cyan)),
Span::styled(
" Enter: eval · ↑↓: history · Sh-↑↓: select row · o: expand · /: filter · Esc: tree",
Style::default().fg(t.comment).bg(t.bg_dark),
),
])
};
frame.render_widget(
Paragraph::new(line).style(Style::default().bg(t.bg_dark)),
area,
);
}
fn draw_history(frame: &mut Frame, app: &App, p: &DapReplPane, area: Rect) {
let t = theme::cur();
let body_h = area.height as usize;
let total = p.history.len();
let visible: Vec<usize> = p.visible_history_indices();
let visible_count = visible.len();
let mut lines: Vec<Line> = Vec::new();
if total == 0 {
lines.push(Line::from(Span::styled(
" (no evaluations yet — type an expression below)",
Style::default().fg(t.comment).bg(t.bg_dark),
)));
} else if visible_count == 0 {
lines.push(Line::from(Span::styled(
format!(" No matches for \"{}\" — Esc clears", p.filter),
Style::default().fg(t.comment).bg(t.bg_dark),
)));
} else {
let render_from_in_visible = if p.scroll == usize::MAX {
let mut budget = body_h;
let mut idx = visible_count;
while idx > 0 {
let e = &p.history[visible[idx - 1]];
let rows = entry_render_rows(app, e);
if budget < rows {
break;
}
budget -= rows;
idx -= 1;
}
idx
} else {
visible
.iter()
.position(|&i| i >= p.scroll)
.unwrap_or(visible_count.saturating_sub(1))
};
for &i in visible.iter().skip(render_from_in_visible) {
if lines.len() >= body_h {
break;
}
let entry = &p.history[i];
let selected = p.selected == Some(i);
let chip_bg = if selected { t.bg2 } else { t.bg_dark };
let expand_chip = if entry.variables_ref > 0 {
format!("{} ", crate::ui::expand_glyph(app, entry.expanded))
} else {
" ".to_string()
};
lines.push(Line::from(vec![
Span::styled(
format!("{}▶ ", if selected { "● " } else { " " }),
Style::default().fg(t.cyan).bg(chip_bg),
),
Span::styled(expand_chip, Style::default().fg(t.comment).bg(chip_bg)),
Span::styled(
entry.expression.clone(),
Style::default().fg(t.fg).bg(chip_bg),
),
]));
let (text, fg) = if entry.pending {
(" (evaluating…)".to_string(), t.comment)
} else if let Some(err) = &entry.err {
(format!(" err: {err}"), t.red)
} else if let Some(ty) = &entry.ty {
(format!(" = {} : {ty}", entry.value), t.green)
} else {
(format!(" = {}", entry.value), t.green)
};
lines.push(Line::from(Span::styled(
text,
Style::default().fg(fg).bg(chip_bg),
)));
if entry.expanded && entry.variables_ref > 0 {
let kids = app
.dap
.as_ref()
.and_then(|m| m.variables.get(&entry.variables_ref));
if let Some(kids) = kids {
for k in kids {
let kid_chip = match &k.ty {
Some(ty) if !ty.is_empty() => {
format!(" {} : {} = {}", k.name, ty, k.value)
}
_ => format!(" {} = {}", k.name, k.value),
};
lines.push(Line::from(Span::styled(
kid_chip,
Style::default().fg(t.fg).bg(t.bg_dark),
)));
}
} else {
lines.push(Line::from(Span::styled(
" (fetching children…)".to_string(),
Style::default().fg(t.comment).bg(t.bg_dark),
)));
}
}
}
}
frame.render_widget(
Paragraph::new(lines).style(Style::default().bg(t.bg_dark)),
area,
);
}
fn entry_render_rows(app: &App, entry: &crate::pane::DapReplEntry) -> usize {
let mut rows = 2;
if entry.expanded && entry.variables_ref > 0 {
let kid_count = app
.dap
.as_ref()
.and_then(|m| m.variables.get(&entry.variables_ref))
.map(|v| v.len())
.unwrap_or(1); rows += kid_count;
}
rows
}
fn draw_input(frame: &mut Frame, p: &DapReplPane, area: Rect, focused: bool) {
let t = theme::cur();
let prompt = "(repl) > ";
let mut spans = vec![Span::styled(
prompt,
Style::default()
.fg(t.yellow)
.bg(t.bg_dark)
.add_modifier(Modifier::BOLD),
)];
let before: String = p.input[..p.cursor].to_string();
let after: String = p.input[p.cursor..].to_string();
spans.push(Span::styled(
before,
Style::default().fg(t.fg).bg(t.bg_dark),
));
if focused {
spans.push(Span::styled(
"▏",
Style::default()
.fg(t.yellow)
.bg(t.bg_dark)
.add_modifier(Modifier::BOLD),
));
}
spans.push(Span::styled(after, Style::default().fg(t.fg).bg(t.bg_dark)));
frame.render_widget(
Paragraph::new(Line::from(spans)).style(Style::default().bg(t.bg_dark)),
area,
);
}