#[cfg(test)]
mod tests;
mod draw;
mod item;
mod layout;
mod span;
mod unicode;
use std::{
collections::{BTreeMap, btree_map::Entry},
num::NonZero,
ops::Range,
sync::Arc,
};
use self::{
layout::{reset, resize, selection, update},
unicode::Span,
};
use crate::{Injector, Render, incremental::Incremental};
use nucleo::{
self as nc,
pattern::{CaseMatching as NucleoCaseMatching, Normalization as NucleoNormalization},
};
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MatchListEvent {
Up(usize),
ToggleUp(usize),
Down(usize),
ToggleDown(usize),
DeselectAll,
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: NucleoCaseMatching,
pub normalization: NucleoNormalization,
}
impl MatchListConfig {
pub const fn new() -> Self {
Self {
highlight: true,
reversed: false,
highlight_padding: 3,
scroll_padding: 3,
case_matching: NucleoCaseMatching::Smart,
normalization: NucleoNormalization::Smart,
}
}
}
impl Default for MatchListConfig {
fn default() -> Self {
Self::new()
}
}
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 trait Queued {
type Output<'a, T: Send + Sync + 'static>;
fn is_empty(&self) -> bool;
fn clear(&mut self) -> bool;
fn toggle(&mut self, idx: u32) -> bool;
fn is_queued(&self, idx: u32) -> bool;
fn init(limit: Option<NonZero<u32>>) -> Self;
fn into_only_selection<'a, T: Send + Sync + 'static>(
self,
snapshot: &'a nucleo::Snapshot<T>,
idx: u32,
) -> Self::Output<'a, T>;
fn into_selection<'a, T: Send + Sync + 'static>(
self,
snapshot: &'a nucleo::Snapshot<T>,
) -> Self::Output<'a, T>;
}
impl Queued for () {
type Output<'a, T: Send + Sync + 'static> = Option<&'a T>;
#[inline]
fn is_empty(&self) -> bool {
true
}
#[inline]
fn clear(&mut self) -> bool {
false
}
#[inline]
fn toggle(&mut self, _: u32) -> bool {
false
}
#[inline]
fn is_queued(&self, _: u32) -> bool {
false
}
#[inline]
fn init(_: Option<NonZero<u32>>) -> Self {}
#[inline]
fn into_selection<'a, T: Send + Sync + 'static>(
self,
_: &'a nucleo::Snapshot<T>,
) -> Self::Output<'a, T> {
None
}
#[inline]
fn into_only_selection<'a, T: Send + Sync + 'static>(
self,
snapshot: &'a nucleo::Snapshot<T>,
idx: u32,
) -> Self::Output<'a, T> {
Some(snapshot.get_item(idx).unwrap().data)
}
}
impl Queued for SelectedIndices {
type Output<'a, T: Send + Sync + 'static> = Selection<'a, T>;
#[inline]
fn is_empty(&self) -> bool {
self.inner.is_empty()
}
#[inline]
fn clear(&mut self) -> bool {
if self.is_empty() {
false
} else {
self.clear();
true
}
}
#[inline]
fn toggle(&mut self, idx: u32) -> bool {
let n = self.inner.len() as u32;
match self.inner.entry(idx) {
Entry::Occupied(occupied_entry) => {
occupied_entry.remove_entry();
true
}
Entry::Vacant(vacant_entry) => {
if self.limit.is_none_or(|l| n < l.get()) {
vacant_entry.insert(());
true
} else {
false
}
}
}
}
#[inline]
fn is_queued(&self, idx: u32) -> bool {
self.inner.contains_key(&idx)
}
#[inline]
fn init(limit: Option<NonZero<u32>>) -> Self {
Self {
inner: BTreeMap::new(),
limit,
}
}
#[inline]
fn into_selection<'a, T: Send + Sync + 'static>(
self,
snapshot: &'a nucleo::Snapshot<T>,
) -> Self::Output<'a, T> {
Self::Output {
snapshot,
queued: self,
}
}
#[inline]
fn into_only_selection<'a, T: Send + Sync + 'static>(
mut self,
snapshot: &'a nucleo::Snapshot<T>,
idx: u32,
) -> Self::Output<'a, T> {
self.inner.insert(idx, ());
Self::Output {
snapshot,
queued: self,
}
}
}
pub struct SelectedIndices {
inner: BTreeMap<u32, ()>,
limit: Option<NonZero<u32>>,
}
pub struct Selection<'a, T: Send + Sync + 'static> {
snapshot: &'a nc::Snapshot<T>,
queued: SelectedIndices,
}
impl<'a, T: Send + Sync + 'static> Selection<'a, T> {
pub fn iter(&self) -> impl ExactSizeIterator<Item = &'a T> + DoubleEndedIterator {
self.queued.inner.keys().map(|idx| {
unsafe { self.snapshot.get_item_unchecked(*idx).data }
})
}
pub fn is_empty(&self) -> bool {
self.queued.inner.is_empty()
}
pub fn len(&self) -> usize {
self.queued.inner.len()
}
}
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> 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>
where
R: Render<T>,
{
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 {
true
}
}
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)
}
fn idx_from_match_unchecked(&self, n: u32) -> u32 {
self.nucleo
.snapshot()
.matches()
.get(n as usize)
.unwrap()
.idx
}
pub fn toggle_queued_item<Q: Queued>(&mut self, queued_items: &mut Q, n: u32) -> bool {
queued_items.toggle(self.idx_from_match_unchecked(n))
}
pub fn select_none<Q: Queued>(&self, mut queued_items: Q) -> Q::Output<'_, T> {
queued_items.clear();
self.select_queued(queued_items)
}
pub fn select_one<Q: Queued>(&self, queued_items: Q, n: u32) -> Q::Output<'_, T> {
let idx = self.idx_from_match_unchecked(n);
let snapshot = self.nucleo.snapshot();
queued_items.into_only_selection(snapshot, idx)
}
pub fn select_queued<Q: Queued>(&self, queued_items: Q) -> Q::Output<'_, T> {
let snapshot = self.nucleo.snapshot();
queued_items.into_selection(snapshot)
}
pub fn selection_range(&self) -> std::ops::RangeInclusive<usize> {
if self.config.reversed {
self.selection as usize - self.above.len()
..=self.selection as usize + self.below.len() - 1
} else {
self.selection as usize + 1 - self.below.len()
..=self.selection as usize + self.above.len()
}
}
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
}
}
#[cfg(test)]
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)
}
#[cfg(test)]
pub fn selection_decr(&mut self, decrease: u32) -> bool {
let new_selection = self.selection.saturating_sub(decrease);
self.set_selection(new_selection)
}
}