use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Clear, List, ListItem};
use crate::app::{App, Prompt};
const MAX_WIDTH: u16 = 60;
const MAX_HEIGHT: u16 = 10;
pub(super) fn draw_code_action_menu(f: &mut Frame, app: &App, buf_area: Rect) {
let Prompt::CodeActionMenu { actions, selected } = &app.prompt.state else {
return;
};
if actions.is_empty() {
return;
}
let cursor_row = app.buffer.cursor.row;
let Some(rel_y) = app.visual_row_offset(cursor_row) else {
return;
};
let gutter_width: u16 = 1 + 5;
let cursor_x = buf_area.x + gutter_width + app.buffer.cursor.col as u16;
let cursor_y = buf_area.y + rel_y;
let inner_w = actions
.iter()
.map(|a| a.title.chars().count() as u16)
.max()
.unwrap_or(0)
.min(MAX_WIDTH);
let popup_w = (inner_w + 2).min(buf_area.width); let popup_h = (actions.len() as u16 + 2).min(MAX_HEIGHT + 2);
let below_y = cursor_y.saturating_add(1);
let space_below = buf_area.bottom().saturating_sub(below_y);
let (y, _below) = if space_below >= popup_h {
(below_y, true)
} else if cursor_y >= buf_area.y + popup_h {
(cursor_y - popup_h, false)
} else {
(below_y.min(buf_area.bottom().saturating_sub(1)), true)
};
let max_x = buf_area.right().saturating_sub(popup_w);
let x = cursor_x.min(max_x).max(buf_area.x);
let area = Rect {
x,
y,
width: popup_w,
height: popup_h.min(buf_area.bottom().saturating_sub(y)),
};
if area.width <= 2 || area.height <= 2 {
return;
}
f.render_widget(Clear, area);
let block = Block::default()
.borders(Borders::ALL)
.title(" code actions ")
.style(Style::default().bg(Color::Rgb(30, 30, 40)));
let inner = block.inner(area);
f.render_widget(block, area);
let body_h = inner.height as usize;
let scroll = selected.saturating_sub(body_h.saturating_sub(1));
let inner_w = inner.width as usize;
let items: Vec<ListItem> = actions
.iter()
.enumerate()
.skip(scroll)
.take(body_h)
.map(|(i, a)| {
let is_sel = i == *selected;
let title = truncate(&a.title, inner_w);
let style = if is_sel {
Style::default()
.bg(Color::Rgb(58, 78, 122))
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
ListItem::new(Line::from(Span::styled(title, style)))
})
.collect();
f.render_widget(List::new(items), inner);
}
fn truncate(s: &str, max: usize) -> String {
if s.chars().count() <= max {
return s.to_string();
}
if max == 0 {
return String::new();
}
let mut out: String = s.chars().take(max.saturating_sub(1)).collect();
out.push('…');
out
}