#[cfg(test)]
mod tests;
mod draw;
mod item;
mod layout;
mod span;
mod unicode;
use std::{ops::Range, sync::Arc};
use self::{
layout::{reset, resize, selection, update},
unicode::Span,
};
use crate::{incremental::Incremental, Injector, Render};
use nucleo::{
self as nc,
pattern::{CaseMatching, Normalization},
};
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MatchListEvent {
Up(usize),
Down(usize),
Reset,
}
pub trait ItemSize {
fn size(&self) -> usize;
}
pub trait ItemList {
type Item<'a>: ItemSize
where
Self: 'a;
fn total(&self) -> u32;
fn lower(&self, cursor: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
fn lower_inclusive(&self, cursor: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
fn higher(&self, cursor: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
fn higher_inclusive(&self, selection: u32) -> impl DoubleEndedIterator<Item = Self::Item<'_>>;
}
trait ItemListExt: ItemList {
fn sizes_lower<'a>(
&self,
cursor: u32,
vec: &'a mut Vec<usize>,
) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
vec.clear();
Incremental::new(vec, self.lower(cursor).map(|item| item.size()))
}
fn sizes_lower_inclusive<'a>(
&self,
cursor: u32,
vec: &'a mut Vec<usize>,
) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
vec.clear();
Incremental::new(vec, self.lower_inclusive(cursor).map(|item| item.size()))
}
fn sizes_higher<'a>(
&self,
cursor: u32,
vec: &'a mut Vec<usize>,
) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
vec.clear();
Incremental::new(vec, self.higher(cursor).map(|item| item.size()))
}
fn sizes_higher_inclusive<'a>(
&self,
cursor: u32,
vec: &'a mut Vec<usize>,
) -> Incremental<&'a mut Vec<usize>, impl Iterator<Item = usize>> {
vec.clear();
Incremental::new(vec, self.higher_inclusive(cursor).map(|item| item.size()))
}
}
impl<B: ItemList> ItemListExt for B {}
#[derive(Debug)]
struct MatchListState {
selection: u32,
below: u16,
above: u16,
size: u16,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct MatchListConfig {
pub highlight: bool,
pub reversed: bool,
pub highlight_padding: u16,
pub scroll_padding: u16,
pub case_matching: CaseMatching,
pub normalization: Normalization,
}
impl Default for MatchListConfig {
fn default() -> Self {
Self {
highlight: true,
reversed: false,
highlight_padding: 3,
scroll_padding: 3,
case_matching: CaseMatching::default(),
normalization: Normalization::default(),
}
}
}
pub struct IndexBuffer {
spans: Vec<Span>,
lines: Vec<Range<usize>>,
indices: Vec<u32>,
}
impl IndexBuffer {
pub fn new() -> Self {
Self {
spans: Vec::with_capacity(16),
lines: Vec::with_capacity(4),
indices: Vec::with_capacity(16),
}
}
}
pub struct MatchList<T: Send + Sync + 'static, R> {
selection: u32,
size: u16,
below: Vec<usize>,
above: Vec<usize>,
config: MatchListConfig,
nucleo: nc::Nucleo<T>,
scratch: IndexBuffer,
render: Arc<R>,
matcher: nc::Matcher,
prompt: String,
}
impl<T: Send + Sync + 'static, R: Render<T>> MatchList<T, R> {
pub fn new(
config: MatchListConfig,
nucleo_config: nc::Config,
nucleo: nc::Nucleo<T>,
render: Arc<R>,
) -> Self {
Self {
size: 0,
selection: 0,
below: Vec::with_capacity(128),
above: Vec::with_capacity(128),
config,
nucleo,
matcher: nc::Matcher::new(nucleo_config),
render,
scratch: IndexBuffer::new(),
prompt: String::with_capacity(32),
}
}
pub fn reversed(&self) -> bool {
self.config.reversed
}
pub fn render<'a>(&self, item: &'a T) -> <R as Render<T>>::Str<'a> {
self.render.render(item)
}
pub fn reset_renderer(&mut self, render: R) {
self.restart();
self.render = render.into();
}
pub fn injector(&self) -> Injector<T, R> {
Injector::new(self.nucleo.injector(), self.render.clone())
}
pub fn restart(&mut self) {
self.nucleo.restart(true);
self.update_items();
}
pub fn update_nucleo_config(&mut self, config: nc::Config) {
self.nucleo.update_config(config);
}
fn state(&self) -> MatchListState {
let below = self.below.iter().sum::<usize>() as u16;
let above = self.above.iter().sum::<usize>() as u16;
MatchListState {
selection: self.selection,
below: self.size - above,
above: self.size - below,
size: self.size,
}
}
fn whitespace(&self) -> u16 {
self.size
- self.below.iter().sum::<usize>() as u16
- self.above.iter().sum::<usize>() as u16
}
pub fn padding(&self, size: u16) -> u16 {
self.config.scroll_padding.min(size.saturating_sub(1) / 2)
}
pub fn reparse(&mut self, new: &str) {
let appending = match new.strip_prefix(&self.prompt) {
Some(rest) => {
if rest.is_empty() {
return;
} else {
(self
.prompt
.bytes()
.rev()
.take_while(|ch| *ch == b'\\')
.count()
% 2)
== 0
}
}
None => false,
};
self.nucleo.pattern.reparse(
0,
new,
self.config.case_matching,
self.config.normalization,
appending,
);
self.prompt = new.to_owned();
}
pub fn is_empty(&self) -> bool {
self.nucleo.snapshot().matched_item_count() == 0
}
pub fn selection(&self) -> u32 {
self.selection
}
pub fn max_selection(&self) -> u32 {
self.nucleo
.snapshot()
.matched_item_count()
.saturating_sub(1)
}
pub fn get_item(&self, n: u32) -> Option<nc::Item<'_, T>> {
self.nucleo.snapshot().get_matched_item(n)
}
pub fn selection_range(&self) -> std::ops::RangeInclusive<u32> {
if self.config.reversed {
self.selection - self.above.len() as u32..=self.selection + self.below.len() as u32 - 1
} else {
self.selection + 1 - self.below.len() as u32..=self.selection + self.above.len() as u32
}
}
pub fn resize(&mut self, total_size: u16) {
if total_size == 0 {
self.size = 0;
self.above.clear();
self.below.clear();
return;
}
let buffer = self.nucleo.snapshot();
if buffer.total() == 0 {
self.size = total_size;
return;
}
let padding = self.padding(total_size);
let mut previous = self.state();
if self.config.reversed {
previous.below = previous.below.clamp(padding, total_size - padding - 1);
let sizes_below_incl = buffer.sizes_higher_inclusive(self.selection, &mut self.below);
let sizes_above = buffer.sizes_lower(self.selection, &mut self.above);
if self.size <= total_size {
resize::larger_rev(previous, total_size, padding, sizes_below_incl, sizes_above);
} else {
resize::smaller_rev(
previous,
total_size,
padding,
padding,
sizes_below_incl,
sizes_above,
);
}
} else {
previous.above = previous.above.clamp(padding, total_size - padding - 1);
let sizes_below_incl = buffer.sizes_lower_inclusive(self.selection, &mut self.below);
let sizes_above = buffer.sizes_higher(self.selection, &mut self.above);
if self.size <= total_size {
resize::larger(previous, total_size, sizes_below_incl, sizes_above);
} else {
resize::smaller(previous, total_size, padding, sizes_below_incl, sizes_above);
}
}
self.size = total_size;
}
pub fn update(&mut self, millis: u64) -> bool {
let status = self.nucleo.tick(millis);
if status.changed {
self.update_items();
}
status.changed
}
pub fn reset(&mut self) -> bool {
let buffer = self.nucleo.snapshot();
let padding = self.padding(self.size);
if self.selection != 0 {
if self.config.reversed {
let sizes_below_incl = buffer.sizes_higher_inclusive(0, &mut self.below);
self.above.clear();
reset::reset_rev(self.size, sizes_below_incl);
} else {
let sizes_below_incl = buffer.sizes_lower_inclusive(0, &mut self.below);
let sizes_above = buffer.sizes_higher(0, &mut self.above);
reset::reset(self.size, padding, sizes_below_incl, sizes_above);
}
self.selection = 0;
true
} else {
false
}
}
pub fn update_items(&mut self) {
let buffer = self.nucleo.snapshot();
self.selection = self.selection.min(buffer.total().saturating_sub(1));
let previous = self.state();
let padding = self.padding(self.size);
if buffer.total() > 0 {
if self.config.reversed {
let sizes_below_incl =
buffer.sizes_higher_inclusive(self.selection, &mut self.below);
let sizes_above = buffer.sizes_lower(self.selection, &mut self.above);
update::items_rev(previous, padding, sizes_below_incl, sizes_above);
} else {
let sizes_below_incl =
buffer.sizes_lower_inclusive(self.selection, &mut self.below);
let sizes_above = buffer.sizes_higher(self.selection, &mut self.above);
update::items(previous, padding, sizes_below_incl, sizes_above);
}
} else {
self.below.clear();
self.above.clear();
self.selection = 0;
}
}
#[inline]
pub fn set_selection(&mut self, new_selection: u32) -> bool {
let buffer = self.nucleo.snapshot();
let new_selection = new_selection.min(buffer.total().saturating_sub(1));
let previous = self.state();
let padding = self.padding(self.size);
if new_selection == 0 {
self.reset()
} else if new_selection > self.selection {
if self.config.reversed {
let sizes_below_incl =
buffer.sizes_higher_inclusive(new_selection, &mut self.below);
let sizes_above = buffer.sizes_lower(new_selection, &mut self.above);
selection::incr_rev(
previous,
new_selection,
padding,
padding,
sizes_below_incl,
sizes_above,
);
} else {
let sizes_below_incl = buffer.sizes_lower_inclusive(new_selection, &mut self.below);
let sizes_above = buffer.sizes_higher(new_selection, &mut self.above);
selection::incr(
previous,
new_selection,
padding,
sizes_below_incl,
sizes_above,
);
}
self.selection = new_selection;
true
} else if new_selection < self.selection {
if self.config.reversed {
let sizes_below_incl =
buffer.sizes_higher_inclusive(new_selection, &mut self.below);
let sizes_above = buffer.sizes_lower(new_selection, &mut self.above);
selection::decr_rev(
previous,
new_selection,
padding,
sizes_below_incl,
sizes_above,
);
} else {
let sizes_below_incl = buffer.sizes_lower_inclusive(new_selection, &mut self.below);
let sizes_above = buffer.sizes_higher(new_selection, &mut self.above);
selection::decr(
previous,
new_selection,
padding,
padding,
sizes_below_incl,
sizes_above,
);
}
self.selection = new_selection;
true
} else {
false
}
}
pub fn selection_incr(&mut self, increase: u32) -> bool {
let new_selection = self
.selection
.saturating_add(increase)
.min(self.nucleo.snapshot().total().saturating_sub(1));
self.set_selection(new_selection)
}
pub fn selection_decr(&mut self, decrease: u32) -> bool {
let new_selection = self.selection.saturating_sub(decrease);
self.set_selection(new_selection)
}
}