use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Clear, Padding, Paragraph};
use crate::app::App;
use crate::lsp::ParameterLabel;
use crate::text_width::str_cell_width;
const MAX_WIDTH: u16 = 80;
const MAX_HEIGHT: u16 = 8;
pub(super) fn draw_signature(f: &mut Frame, app: &App, buf_area: Rect) {
let Some(state) = app.signature.as_ref() else {
return;
};
let Some(sig) = state.help.signatures.get(state.help.active_signature) else {
return;
};
let active_param = sig.active_parameter.or(state.help.active_parameter);
let row = app.buffer.cursor.row;
let Some(rel_y) = app.visual_row_offset(row) else {
return;
};
let gutter_width: u16 = 1 + 5;
let anchor_x = buf_area.x + gutter_width + app.cursor_visual_col() as u16;
let anchor_y = buf_area.y + rel_y;
let line = build_signature_line(sig, active_param);
let text_len: u16 = line
.spans
.iter()
.map(|s| str_cell_width(&s.content) as u16)
.sum();
let popup_w = (text_len + 4).min(MAX_WIDTH).min(buf_area.width);
let text_w = popup_w.saturating_sub(4) as usize;
if text_w == 0 {
return;
}
let row_count = wrapped_row_count(&line, text_w).max(1) as u16;
let popup_h = (row_count + 2).min(MAX_HEIGHT + 2);
let space_above = anchor_y.saturating_sub(buf_area.y);
let space_below = buf_area.bottom().saturating_sub(anchor_y.saturating_add(1));
let y = if space_above >= popup_h {
anchor_y - popup_h
} else if space_below >= popup_h {
anchor_y.saturating_add(1)
} else if space_above >= 3 {
anchor_y.saturating_sub(popup_h.min(space_above))
} else {
anchor_y
.saturating_add(1)
.min(buf_area.bottom().saturating_sub(1))
};
let max_x = buf_area.right().saturating_sub(popup_w);
let x = anchor_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)
.border_style(Style::default().fg(super::PANEL_BORDER_FG))
.padding(Padding::horizontal(1))
.style(Style::default().bg(super::PANEL_BG));
let inner = block.inner(area);
f.render_widget(block, area);
let para = Paragraph::new(line).wrap(ratatui::widgets::Wrap { trim: false });
f.render_widget(para, inner);
}
fn build_signature_line(
sig: &crate::lsp::SignatureInformation,
active: Option<usize>,
) -> Line<'static> {
let label = &sig.label;
let base = Style::default().fg(Color::Gray);
let highlight = Style::default()
.fg(Color::Yellow)
.add_modifier(Modifier::BOLD);
let range = active
.and_then(|i| sig.parameters.get(i))
.and_then(|p| resolve_param_range(&p.label, label));
match range {
Some((start, end)) if start < end && end <= label.chars().count() => {
let chars: Vec<char> = label.chars().collect();
let before: String = chars[..start].iter().collect();
let active_text: String = chars[start..end].iter().collect();
let after: String = chars[end..].iter().collect();
Line::from(vec![
Span::styled(before, base),
Span::styled(active_text, highlight),
Span::styled(after, base),
])
}
_ => Line::from(Span::styled(label.clone(), base)),
}
}
fn resolve_param_range(label: &ParameterLabel, signature_label: &str) -> Option<(usize, usize)> {
match label {
ParameterLabel::Offsets(s, e) => Some((*s as usize, *e as usize)),
ParameterLabel::Text(t) => {
let sig_chars: Vec<char> = signature_label.chars().collect();
let needle_chars: Vec<char> = t.chars().collect();
if needle_chars.is_empty() || needle_chars.len() > sig_chars.len() {
return None;
}
let last_start = sig_chars.len() - needle_chars.len();
for i in 0..=last_start {
if sig_chars[i..i + needle_chars.len()] == needle_chars[..] {
return Some((i, i + needle_chars.len()));
}
}
None
}
}
}
fn wrapped_row_count(line: &Line, width: usize) -> usize {
let total: usize = line.spans.iter().map(|s| str_cell_width(&s.content)).sum();
if total == 0 {
return 1;
}
total.div_ceil(width)
}