feat(core): Expand filtering into pipeline supporting multiple text search modes.

Modes include: exact match, smart-case, and regular expressions.
This commit is contained in:
2026-03-13 22:55:47 -04:00
parent 6187b83f26
commit 6a4cc85285
10 changed files with 1352 additions and 10 deletions

View File

@@ -113,6 +113,34 @@ impl Viewport {
}
}
/// Move cursor up by `n` half-pages (half viewport height
/// each). Clamps to the top.
pub fn half_page_up(&mut self, n: usize) {
if self.height == 0 {
return;
}
let half = (self.height / 2).max(1);
let distance = half.saturating_mul(n);
self.cursor = self.cursor.saturating_sub(distance);
if self.cursor < self.scroll_offset {
self.scroll_offset = self.cursor;
}
}
/// Move cursor down by `n` half-pages (half viewport height
/// each). Clamps to the last item.
pub fn half_page_down(&mut self, n: usize) {
if self.height == 0 || self.filtered_count == 0 {
return;
}
let half = (self.height / 2).max(1);
let distance = half.saturating_mul(n);
self.cursor = (self.cursor + distance).min(self.filtered_count - 1);
if self.cursor >= self.scroll_offset + self.height {
self.scroll_offset = self.cursor - self.height + 1;
}
}
/// Move cursor down by `n` pages (viewport height each).
/// Clamps to the last item.
pub fn page_down(&mut self, n: usize) {
@@ -353,4 +381,106 @@ mod tests {
v.set_height(3);
assert!(v.cursor() < v.scroll_offset() + 3);
}
// -- half_page unit tests --
#[test]
fn half_page_down_basic() {
let mut v = viewport(10, 30);
v.half_page_down(1);
assert_eq!(v.cursor(), 5);
assert_eq!(v.scroll_offset(), 0);
}
#[test]
fn half_page_down_scrolls() {
let mut v = viewport(10, 30);
// Move cursor near viewport edge, then half page down
v.half_page_down(1); // cursor 5
v.half_page_down(1); // cursor 10, should scroll
assert_eq!(v.cursor(), 10);
assert_eq!(v.scroll_offset(), 1);
}
#[test]
fn half_page_down_clamps() {
let mut v = viewport(10, 12);
v.half_page_down(1); // cursor 5
v.half_page_down(1); // cursor 10
v.half_page_down(1); // clamps to 11
assert_eq!(v.cursor(), 11);
}
#[test]
fn half_page_down_n_multiplier() {
let mut v = viewport(10, 30);
v.half_page_down(3); // (10/2)*3 = 15
assert_eq!(v.cursor(), 15);
}
#[test]
fn half_page_up_basic() {
let mut v = viewport(10, 30);
v.half_page_down(2); // cursor 10
v.half_page_up(1); // cursor 5
assert_eq!(v.cursor(), 5);
}
#[test]
fn half_page_up_clamps_at_top() {
let mut v = viewport(10, 30);
v.half_page_down(1); // cursor 5
v.half_page_up(1); // cursor 0
assert_eq!(v.cursor(), 0);
v.half_page_up(1); // still 0
assert_eq!(v.cursor(), 0);
}
#[test]
fn half_page_up_scrolls() {
let mut v = viewport(10, 30);
// Scroll down far enough that offset > 0
v.half_page_down(3); // cursor 15, offset 6
assert!(v.scroll_offset() > 0);
// Now half page up should track cursor back
v.half_page_up(1); // cursor 10
v.half_page_up(1); // cursor 5
assert_eq!(v.cursor(), 5);
// Offset should have followed cursor if it went above
assert!(v.scroll_offset() <= v.cursor());
}
#[test]
fn half_page_height_one() {
let mut v = viewport(1, 10);
// max(1/2, 1) = 1, moves 1 item
v.half_page_down(1);
assert_eq!(v.cursor(), 1);
}
#[test]
fn half_page_height_two() {
let mut v = viewport(2, 10);
// 2/2 = 1, moves 1 item
v.half_page_down(1);
assert_eq!(v.cursor(), 1);
}
#[test]
fn half_page_zero_height() {
let mut v = viewport(0, 10);
v.half_page_down(1);
assert_eq!(v.cursor(), 0);
v.half_page_up(1);
assert_eq!(v.cursor(), 0);
}
#[test]
fn half_page_empty_list() {
let mut v = viewport(10, 0);
v.half_page_down(1);
assert_eq!(v.cursor(), 0);
v.half_page_up(1);
assert_eq!(v.cursor(), 0);
}
}