use std::io::{self, Write};
use nucleo as nc;
use super::{
IndexBuffer, MatchList, MatchListConfig, MatchListEvent,
item::RenderedItem,
span::{Head, KeepLines, Spanned, Tail},
unicode::{AsciiProcessor, UnicodeProcessor},
};
use crate::{
Render,
component::Component,
util::{as_u16, as_u32},
};
use crossterm::{
QueueableCommand,
cursor::MoveToNextLine,
style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
terminal::{Clear, ClearType},
};
#[inline]
#[allow(clippy::too_many_arguments)]
fn draw_single_match<
T: Send + Sync + 'static,
R: Render<T>,
L: KeepLines,
W: Write + ?Sized,
const SELECTED: bool,
>(
writer: &mut W,
buffer: &mut IndexBuffer,
max_draw_length: u16, config: &MatchListConfig,
item: &nc::Item<'_, T>,
snapshot: &nc::Snapshot<T>,
matcher: &mut nc::Matcher,
height: u16,
render: &R,
) -> io::Result<()> {
if config.highlight {
buffer.indices.clear();
snapshot.pattern().column_pattern(0).indices(
item.matcher_columns[0].slice(..),
matcher,
&mut buffer.indices,
);
buffer.indices.sort_unstable();
buffer.indices.dedup();
}
match RenderedItem::new(item, render) {
RenderedItem::Ascii(s) => Spanned::<'_, AsciiProcessor>::new(
&buffer.indices,
s,
&mut buffer.spans,
&mut buffer.lines,
L::from_offset(height),
)
.queue_print(writer, SELECTED, max_draw_length, config.highlight_padding),
RenderedItem::Unicode(r) => Spanned::<'_, UnicodeProcessor>::new(
&buffer.indices,
r.as_ref(),
&mut buffer.spans,
&mut buffer.lines,
L::from_offset(height),
)
.queue_print(writer, SELECTED, max_draw_length, config.highlight_padding),
}
}
#[allow(clippy::too_many_arguments)]
fn draw_matches<'a, T: Send + Sync + 'static, R: Render<T>, W: io::Write + ?Sized>(
writer: &mut W,
buffer: &mut IndexBuffer,
config: &MatchListConfig,
snapshot: &nc::Snapshot<T>,
matcher: &mut nc::Matcher,
render: &R,
match_list_width: u16,
above: &[usize],
below: &[usize],
mut item_iter: impl Iterator<Item = nc::Item<'a, T>>,
) -> io::Result<()> {
for (item_height, item) in above.iter().rev().zip(item_iter.by_ref()) {
draw_single_match::<_, _, Tail, _, false>(
writer,
buffer,
match_list_width,
config,
&item,
snapshot,
matcher,
as_u16(*item_height),
render,
)?;
}
draw_single_match::<_, _, Head, _, true>(
writer,
buffer,
match_list_width,
config,
&item_iter.next().unwrap(),
snapshot,
matcher,
as_u16(below[0]),
render,
)?;
for (item_height, item) in below[1..].iter().zip(item_iter.by_ref()) {
draw_single_match::<_, _, Head, _, false>(
writer,
buffer,
match_list_width,
config,
&item,
snapshot,
matcher,
as_u16(*item_height),
render,
)?;
}
Ok(())
}
fn draw_match_counts<W: io::Write + ?Sized>(
writer: &mut W,
matched: u32,
total: u32,
) -> io::Result<()> {
writer
.queue(SetAttribute(Attribute::Italic))?
.queue(SetForegroundColor(Color::Green))?
.queue(Print(" "))?
.queue(Print(matched))?
.queue(Print("/"))?
.queue(Print(total))?
.queue(SetAttribute(Attribute::Reset))?
.queue(ResetColor)?
.queue(Clear(ClearType::UntilNewLine))?;
Ok(())
}
impl<T: Send + Sync + 'static, R: Render<T>> Component for MatchList<T, R> {
type Event = MatchListEvent;
type Status = bool;
fn handle(&mut self, e: Self::Event) -> bool {
match e {
MatchListEvent::Up(n) => {
if self.config.reversed {
self.selection_decr(as_u32(n))
} else {
self.selection_incr(as_u32(n))
}
}
MatchListEvent::Down(n) => {
if self.config.reversed {
self.selection_incr(as_u32(n))
} else {
self.selection_decr(as_u32(n))
}
}
MatchListEvent::Reset => self.reset(),
}
}
fn draw<W: Write + ?Sized>(
&mut self,
width: u16,
height: u16,
writer: &mut W,
) -> std::io::Result<()> {
let match_list_height = height - 1;
let match_list_width = width.saturating_sub(3);
if match_list_height != self.size {
self.resize(match_list_height);
}
let snapshot = self.nucleo.snapshot();
let matched_item_count = snapshot.matched_item_count();
let mut total_whitespace = self.whitespace();
if height == 1 {
draw_match_counts(writer, matched_item_count, snapshot.item_count())?;
} else if self.config.reversed {
draw_match_counts(writer, matched_item_count, snapshot.item_count())?;
writer.queue(MoveToNextLine(1))?;
if matched_item_count != 0 {
let item_iter = snapshot.matched_items(self.selection_range());
draw_matches(
writer,
&mut self.scratch,
&self.config,
snapshot,
&mut self.matcher,
self.render.as_ref(),
match_list_width,
&self.above,
&self.below,
item_iter,
)?;
}
if total_whitespace > 0 {
writer.queue(Clear(ClearType::FromCursorDown))?;
}
} else {
while total_whitespace > 0 {
total_whitespace -= 1;
writer
.queue(Clear(ClearType::UntilNewLine))?
.queue(MoveToNextLine(1))?;
}
if matched_item_count != 0 {
let item_iter = snapshot.matched_items(self.selection_range()).rev();
draw_matches(
writer,
&mut self.scratch,
&self.config,
snapshot,
&mut self.matcher,
self.render.as_ref(),
match_list_width,
&self.above,
&self.below,
item_iter,
)?;
}
draw_match_counts(writer, matched_item_count, snapshot.item_count())?;
}
Ok(())
}
}