Working search
Some checks failed
Cargo CI / Build and Test (pull_request) Failing after 1m43s
Cargo CI / Lint (pull_request) Successful in 1m14s

This commit is contained in:
Wojciech Kozlowski 2024-03-01 18:48:23 +01:00
parent fd19ea3eb3
commit a381705a1c
3 changed files with 265 additions and 65 deletions

View File

@ -77,7 +77,7 @@ impl<MH: IMusicHoard> IAppInteractBrowse for AppMachine<MH, AppBrowse> {
let orig = ListSelection::get(&self.inner.selection); let orig = ListSelection::get(&self.inner.selection);
self.inner self.inner
.selection .selection
.reset_artist(self.inner.music_hoard.get_collection()); .reset(self.inner.music_hoard.get_collection());
AppMachine::search(self.inner, orig).into() AppMachine::search(self.inner, orig).into()
} }

View File

@ -1,13 +1,13 @@
use aho_corasick::AhoCorasick; use aho_corasick::AhoCorasick;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use musichoard::collection::artist::Artist; use musichoard::collection::{album::Album, artist::Artist, track::Track};
use crate::tui::{ use crate::tui::{
app::{ app::{
machine::{App, AppInner, AppMachine}, machine::{App, AppInner, AppMachine},
selection::ListSelection, selection::ListSelection,
AppPublic, AppState, IAppInteractSearch, AppPublic, AppState, Category, IAppInteractSearch,
}, },
lib::IMusicHoard, lib::IMusicHoard,
}; };
@ -67,7 +67,7 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
fn append_character(mut self, ch: char) -> Self::APP { fn append_character(mut self, ch: char) -> Self::APP {
self.state.string.push(ch); self.state.string.push(ch);
let index = self.inner.selection.artist.state.list.selected(); let index = self.inner.selection.selected();
self.state.memo.push(AppSearchMemo { index, char: true }); self.state.memo.push(AppSearchMemo { index, char: true });
self.incremental_search(false); self.incremental_search(false);
self.into() self.into()
@ -75,7 +75,7 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
fn search_next(mut self) -> Self::APP { fn search_next(mut self) -> Self::APP {
if !self.state.string.is_empty() { if !self.state.string.is_empty() {
let index = self.inner.selection.artist.state.list.selected(); let index = self.inner.selection.selected();
self.state.memo.push(AppSearchMemo { index, char: false }); self.state.memo.push(AppSearchMemo { index, char: false });
self.incremental_search(true); self.incremental_search(true);
} }
@ -88,7 +88,7 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
if memo.char { if memo.char {
self.state.string.pop(); self.state.string.pop();
} }
self.inner.selection.select_artist(collection, memo.index); self.inner.selection.select(collection, memo.index);
} }
self.into() self.into()
} }
@ -109,12 +109,13 @@ impl<MH: IMusicHoard> IAppInteractSearch for AppMachine<MH, AppSearch> {
trait IAppInteractSearchPrivate { trait IAppInteractSearchPrivate {
fn incremental_search(&mut self, next: bool); fn incremental_search(&mut self, next: bool);
fn incremental_search_predicate( fn search_category<T, P>(list: &[T], index: usize, next: bool, pred: P) -> Option<usize>
case_sensitive: bool, where
char_sensitive: bool, P: FnMut(&T) -> bool;
search_name: &str, fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool;
probe: &Artist, fn predicate_albums(case_sens: bool, char_sens: bool, search: &str, probe: &Album) -> bool;
) -> bool; fn predicate_tracks(case_sens: bool, char_sens: bool, search: &str, probe: &Track) -> bool;
fn predicate_title(case_sens: bool, char_sens: bool, search: &str, title: &str) -> bool;
fn is_case_sensitive(artist_name: &str) -> bool; fn is_case_sensitive(artist_name: &str) -> bool;
fn is_char_sensitive(artist_name: &str) -> bool; fn is_char_sensitive(artist_name: &str) -> bool;
@ -123,50 +124,102 @@ trait IAppInteractSearchPrivate {
impl<MH: IMusicHoard> IAppInteractSearchPrivate for AppMachine<MH, AppSearch> { impl<MH: IMusicHoard> IAppInteractSearchPrivate for AppMachine<MH, AppSearch> {
fn incremental_search(&mut self, next: bool) { fn incremental_search(&mut self, next: bool) {
let artists = self.inner.music_hoard.get_collection(); let collection = self.inner.music_hoard.get_collection();
let artist_name = &self.state.string; let search_name = &self.state.string;
let sel = &mut self.inner.selection; let sel = &mut self.inner.selection;
if let Some(mut index) = sel.selected_artist() { if let Some(index) = sel.selected() {
let case_sensitive = Self::is_case_sensitive(artist_name); let case_sensitive = Self::is_case_sensitive(search_name);
let char_sensitive = Self::is_char_sensitive(artist_name); let char_sensitive = Self::is_char_sensitive(search_name);
let search = Self::normalize_search(artist_name, !case_sensitive, !char_sensitive); let search = Self::normalize_search(search_name, !case_sensitive, !char_sensitive);
if next && ((index + 1) < artists.len()) { let result = match sel.active {
Category::Artist => {
let artists = collection;
Self::search_category(artists, index, next, |probe| {
Self::predicate_artists(case_sensitive, char_sensitive, &search, probe)
})
}
Category::Album => {
let artists = collection;
let albums = sel
.artist
.state
.list
.selected()
.map(|i| &artists[i].albums)
.unwrap();
Self::search_category(albums, index, next, |probe| {
Self::predicate_albums(case_sensitive, char_sensitive, &search, probe)
})
}
Category::Track => {
let artists = collection;
let albums = sel
.artist
.state
.list
.selected()
.map(|i| &artists[i].albums)
.unwrap();
let tracks = sel
.artist
.album
.state
.list
.selected()
.map(|i| &albums[i].tracks)
.unwrap();
Self::search_category(tracks, index, next, |probe| {
Self::predicate_tracks(case_sensitive, char_sensitive, &search, probe)
})
}
};
if result.is_some() {
sel.select(collection, result);
}
}
}
fn search_category<T, P>(list: &[T], mut index: usize, next: bool, pred: P) -> Option<usize>
where
P: FnMut(&T) -> bool,
{
if next && ((index + 1) < list.len()) {
index += 1; index += 1;
} }
let slice = &artists[index..]; let slice = &list[index..];
let result = slice.iter().position(pred);
let result = slice.iter().position(|probe| { result.map(|slice_index| index + slice_index)
Self::incremental_search_predicate(case_sensitive, char_sensitive, &search, probe)
});
if let Some(slice_index) = result {
sel.select_artist(artists, Some(index + slice_index));
}
}
} }
fn incremental_search_predicate( fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool {
case_sensitive: bool, let name = Self::normalize_search(&probe.id.name, !case_sens, !char_sens);
char_sensitive: bool, let mut result = name.starts_with(search);
search_name: &str,
probe: &Artist,
) -> bool {
let name = Self::normalize_search(&probe.id.name, !case_sensitive, !char_sensitive);
let mut result = name.starts_with(search_name);
if let Some(ref probe_sort) = probe.sort { if let Some(ref probe_sort) = probe.sort {
if !result { if !result {
let name = let name = Self::normalize_search(&probe_sort.name, !case_sens, !char_sens);
Self::normalize_search(&probe_sort.name, !case_sensitive, !char_sensitive); result = name.starts_with(search);
result = name.starts_with(search_name);
} }
} }
result result
} }
fn predicate_albums(case_sens: bool, char_sens: bool, search: &str, probe: &Album) -> bool {
Self::predicate_title(case_sens, char_sens, search, &probe.id.title)
}
fn predicate_tracks(case_sens: bool, char_sens: bool, search: &str, probe: &Track) -> bool {
Self::predicate_title(case_sens, char_sens, search, &probe.id.title)
}
fn predicate_title(case_sens: bool, char_sens: bool, search: &str, title: &str) -> bool {
Self::normalize_search(title, !case_sens, !char_sens).starts_with(search)
}
fn is_case_sensitive(artist_name: &str) -> bool { fn is_case_sensitive(artist_name: &str) -> bool {
artist_name artist_name
.chars() .chars()

View File

@ -80,20 +80,6 @@ impl Selection {
self.artist.reinitialise(artists, selected.artist); self.artist.reinitialise(artists, selected.artist);
} }
pub fn select_artist(&mut self, artists: &[Artist], index: Option<usize>) {
self.artist.select(artists, index);
}
pub fn selected_artist(&self) -> Option<usize> {
self.artist.selected()
}
pub fn reset_artist(&mut self, artists: &[Artist]) {
if self.artist.state.list.selected() != Some(0) {
self.select_by_id(artists, IdSelection { artist: None });
}
}
pub fn increment_category(&mut self) { pub fn increment_category(&mut self) {
self.active = match self.active { self.active = match self.active {
Category::Artist => Category::Album, Category::Artist => Category::Album,
@ -110,6 +96,66 @@ impl Selection {
}; };
} }
pub fn select(&mut self, collection: &Collection, index: Option<usize>) {
match self.active {
Category::Artist => self.select_artist(collection, index),
Category::Album => self.select_album(collection, index),
Category::Track => self.select_track(collection, index),
}
}
fn select_artist(&mut self, artists: &[Artist], index: Option<usize>) {
self.artist.select(artists, index);
}
fn select_album(&mut self, artists: &[Artist], index: Option<usize>) {
self.artist.select_album(artists, index);
}
fn select_track(&mut self, artists: &[Artist], index: Option<usize>) {
self.artist.select_track(artists, index);
}
pub fn selected(&self) -> Option<usize> {
match self.active {
Category::Artist => self.selected_artist(),
Category::Album => self.selected_album(),
Category::Track => self.selected_track(),
}
}
fn selected_artist(&self) -> Option<usize> {
self.artist.selected()
}
fn selected_album(&self) -> Option<usize> {
self.artist.selected_album()
}
fn selected_track(&self) -> Option<usize> {
self.artist.selected_track()
}
pub fn reset(&mut self, collection: &Collection) {
match self.active {
Category::Artist => self.reset_artist(collection),
Category::Album => self.reset_album(collection),
Category::Track => self.reset_track(collection),
}
}
fn reset_artist(&mut self, artists: &[Artist]) {
self.artist.reset(artists);
}
fn reset_album(&mut self, artists: &[Artist]) {
self.artist.reset_album(artists);
}
fn reset_track(&mut self, artists: &[Artist]) {
self.artist.reset_track(artists);
}
pub fn increment_selection(&mut self, collection: &Collection, delta: Delta) { pub fn increment_selection(&mut self, collection: &Collection, delta: Delta) {
match self.active { match self.active {
Category::Artist => self.increment_artist(collection, delta), Category::Artist => self.increment_artist(collection, delta),
@ -118,6 +164,18 @@ impl Selection {
} }
} }
fn increment_artist(&mut self, artists: &[Artist], delta: Delta) {
self.artist.increment(artists, delta);
}
fn increment_album(&mut self, artists: &[Artist], delta: Delta) {
self.artist.increment_album(artists, delta);
}
fn increment_track(&mut self, artists: &[Artist], delta: Delta) {
self.artist.increment_track(artists, delta);
}
pub fn decrement_selection(&mut self, collection: &Collection, delta: Delta) { pub fn decrement_selection(&mut self, collection: &Collection, delta: Delta) {
match self.active { match self.active {
Category::Artist => self.decrement_artist(collection, delta), Category::Artist => self.decrement_artist(collection, delta),
@ -126,26 +184,14 @@ impl Selection {
} }
} }
fn increment_artist(&mut self, artists: &[Artist], delta: Delta) {
self.artist.increment(artists, delta);
}
fn decrement_artist(&mut self, artists: &[Artist], delta: Delta) { fn decrement_artist(&mut self, artists: &[Artist], delta: Delta) {
self.artist.decrement(artists, delta); self.artist.decrement(artists, delta);
} }
fn increment_album(&mut self, artists: &[Artist], delta: Delta) {
self.artist.increment_album(artists, delta);
}
fn decrement_album(&mut self, artists: &[Artist], delta: Delta) { fn decrement_album(&mut self, artists: &[Artist], delta: Delta) {
self.artist.decrement_album(artists, delta); self.artist.decrement_album(artists, delta);
} }
fn increment_track(&mut self, artists: &[Artist], delta: Delta) {
self.artist.increment_track(artists, delta);
}
fn decrement_track(&mut self, artists: &[Artist], delta: Delta) { fn decrement_track(&mut self, artists: &[Artist], delta: Delta) {
self.artist.decrement_track(artists, delta); self.artist.decrement_track(artists, delta);
} }
@ -197,6 +243,14 @@ impl ArtistSelection {
self.state.list.selected() self.state.list.selected()
} }
fn selected_album(&self) -> Option<usize> {
self.album.selected()
}
fn selected_track(&self) -> Option<usize> {
self.album.selected_track()
}
fn select(&mut self, artists: &[Artist], to: Option<usize>) { fn select(&mut self, artists: &[Artist], to: Option<usize>) {
match to { match to {
Some(to) => self.select_to(artists, to), Some(to) => self.select_to(artists, to),
@ -204,6 +258,18 @@ impl ArtistSelection {
} }
} }
fn select_album(&mut self, artists: &[Artist], to: Option<usize>) {
if let Some(index) = self.state.list.selected() {
self.album.select(&artists[index].albums, to);
}
}
fn select_track(&mut self, artists: &[Artist], to: Option<usize>) {
if let Some(index) = self.state.list.selected() {
self.album.select_track(&artists[index].albums, to);
}
}
fn select_to(&mut self, artists: &[Artist], mut to: usize) { fn select_to(&mut self, artists: &[Artist], mut to: usize) {
to = cmp::min(to, artists.len() - 1); to = cmp::min(to, artists.len() - 1);
if self.state.list.selected() != Some(to) { if self.state.list.selected() != Some(to) {
@ -212,6 +278,24 @@ impl ArtistSelection {
} }
} }
fn reset(&mut self, artists: &[Artist]) {
if self.state.list.selected() != Some(0) {
self.reinitialise(artists, None);
}
}
fn reset_album(&mut self, artists: &[Artist]) {
if let Some(index) = self.state.list.selected() {
self.album.reset(&artists[index].albums);
}
}
fn reset_track(&mut self, artists: &[Artist]) {
if let Some(index) = self.state.list.selected() {
self.album.reset_track(&artists[index].albums);
}
}
fn increment_by(&mut self, artists: &[Artist], by: usize) { fn increment_by(&mut self, artists: &[Artist], by: usize) {
if let Some(index) = self.state.list.selected() { if let Some(index) = self.state.list.selected() {
let result = index.saturating_add(by); let result = index.saturating_add(by);
@ -303,6 +387,47 @@ impl AlbumSelection {
} }
} }
fn selected(&self) -> Option<usize> {
self.state.list.selected()
}
fn selected_track(&self) -> Option<usize> {
self.track.selected()
}
fn select(&mut self, albums: &[Album], to: Option<usize>) {
match to {
Some(to) => self.select_to(albums, to),
None => self.state.list.select(None),
}
}
fn select_track(&mut self, albums: &[Album], to: Option<usize>) {
if let Some(index) = self.state.list.selected() {
self.track.select(&albums[index].tracks, to);
}
}
fn select_to(&mut self, albums: &[Album], mut to: usize) {
to = cmp::min(to, albums.len() - 1);
if self.state.list.selected() != Some(to) {
self.state.list.select(Some(to));
self.track = TrackSelection::initialise(&albums[to].tracks);
}
}
fn reset(&mut self, albums: &[Album]) {
if self.state.list.selected() != Some(0) {
self.reinitialise(albums, None);
}
}
fn reset_track(&mut self, albums: &[Album]) {
if let Some(index) = self.state.list.selected() {
self.track.reset(&albums[index].tracks);
}
}
fn increment_by(&mut self, albums: &[Album], by: usize) { fn increment_by(&mut self, albums: &[Album], by: usize) {
if let Some(index) = self.state.list.selected() { if let Some(index) = self.state.list.selected() {
let mut result = index.saturating_add(by); let mut result = index.saturating_add(by);
@ -377,6 +502,28 @@ impl TrackSelection {
} }
} }
fn selected(&self) -> Option<usize> {
self.state.list.selected()
}
fn select(&mut self, tracks: &[Track], to: Option<usize>) {
match to {
Some(to) => self.select_to(tracks, to),
None => self.state.list.select(None),
}
}
fn select_to(&mut self, tracks: &[Track], mut to: usize) {
to = cmp::min(to, tracks.len() - 1);
self.state.list.select(Some(to));
}
fn reset(&mut self, tracks: &[Track]) {
if self.state.list.selected() != Some(0) {
self.reinitialise(tracks, None);
}
}
fn increment_by(&mut self, tracks: &[Track], by: usize) { fn increment_by(&mut self, tracks: &[Track], by: usize) {
if let Some(index) = self.state.list.selected() { if let Some(index) = self.state.list.selected() {
let mut result = index.saturating_add(by); let mut result = index.saturating_add(by);