use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{List, ListItem, Paragraph};
use crate::finder::{Finder, FuzzyKind};
const DIR_FG: Color = Color::Blue;
const HIT_FG: Color = Color::Magenta;
pub(super) fn draw_fuzzy_list(f: &mut Frame, finder: &Finder, area: Rect) {
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(1),
Constraint::Length(1),
Constraint::Min(1),
])
.split(area);
let query_line = Line::from(vec![
Span::styled("› ", Style::default().fg(Color::Yellow)),
Span::raw(finder.query.clone()),
]);
f.render_widget(Paragraph::new(query_line), chunks[0]);
let sep = "─".repeat(chunks[1].width as usize);
f.render_widget(
Paragraph::new(Span::styled(sep, Style::default().fg(Color::DarkGray))),
chunks[1],
);
let list_h = chunks[2].height as usize;
let list_w = chunks[2].width as usize;
let scroll = finder.selected.saturating_sub(list_h.saturating_sub(1));
let items: Vec<ListItem> = finder
.matches
.iter()
.enumerate()
.skip(scroll)
.take(list_h)
.map(|(i, m)| {
let raw = &finder.items[m.idx];
let line = render_match(
raw,
&m.positions,
i == finder.selected,
finder.kind,
list_w,
);
ListItem::new(line)
})
.collect();
f.render_widget(List::new(items), chunks[2]);
}
fn render_match<'a>(
item: &'a str,
positions: &[usize],
selected: bool,
kind: FuzzyKind,
width: usize,
) -> Line<'a> {
let base = if selected {
Style::default()
.bg(Color::DarkGray)
.add_modifier(Modifier::BOLD)
} else {
Style::default()
};
let dir = base.fg(DIR_FG).add_modifier(Modifier::BOLD);
let hit = base.fg(HIT_FG).add_modifier(Modifier::BOLD);
let ellipsis = base.fg(Color::DarkGray);
let chars: Vec<char> = item.chars().collect();
let dir_end_char: Option<usize> = match kind {
FuzzyKind::Files { .. } | FuzzyKind::Buffers => {
chars.iter().rposition(|c| *c == '/').map(|i| i + 1)
}
FuzzyKind::Locations => {
let path_end = chars.iter().position(|c| *c == ':').unwrap_or(chars.len());
chars[..path_end].iter().rposition(|c| *c == '/').map(|i| i + 1)
}
FuzzyKind::Lines => None,
};
let mut spans = Vec::new();
let start = if width >= 2 && chars.len() > width {
spans.push(Span::styled("…", ellipsis));
chars.len() - (width - 1)
} else {
0
};
let dir_end_visible = dir_end_char.map(|e| e.saturating_sub(start));
let mut buf = String::new();
let mut buf_style = base;
for (offset, &c) in chars[start..].iter().enumerate() {
let orig_i = start + offset;
let is_hit = positions.binary_search(&orig_i).is_ok();
let in_dir = dir_end_visible.map(|e| offset < e).unwrap_or(false);
let style = if is_hit {
hit
} else if in_dir {
dir
} else {
base
};
if style != buf_style && !buf.is_empty() {
spans.push(Span::styled(std::mem::take(&mut buf), buf_style));
}
buf_style = style;
buf.push(c);
}
if !buf.is_empty() {
spans.push(Span::styled(buf, buf_style));
}
Line::from(spans)
}