From a381705a1cfd00e53b96e5e27d474d2ddc54e25e Mon Sep 17 00:00:00 2001 From: Wojciech Kozlowski Date: Fri, 1 Mar 2024 18:48:23 +0100 Subject: [PATCH] Working search --- src/tui/app/machine/browse.rs | 2 +- src/tui/app/machine/search.rs | 129 +++++++++++++++------- src/tui/app/selection.rs | 199 +++++++++++++++++++++++++++++----- 3 files changed, 265 insertions(+), 65 deletions(-) diff --git a/src/tui/app/machine/browse.rs b/src/tui/app/machine/browse.rs index 481ca32..686ef23 100644 --- a/src/tui/app/machine/browse.rs +++ b/src/tui/app/machine/browse.rs @@ -77,7 +77,7 @@ impl IAppInteractBrowse for AppMachine { let orig = ListSelection::get(&self.inner.selection); self.inner .selection - .reset_artist(self.inner.music_hoard.get_collection()); + .reset(self.inner.music_hoard.get_collection()); AppMachine::search(self.inner, orig).into() } diff --git a/src/tui/app/machine/search.rs b/src/tui/app/machine/search.rs index b009389..3a1e886 100644 --- a/src/tui/app/machine/search.rs +++ b/src/tui/app/machine/search.rs @@ -1,13 +1,13 @@ use aho_corasick::AhoCorasick; use once_cell::sync::Lazy; -use musichoard::collection::artist::Artist; +use musichoard::collection::{album::Album, artist::Artist, track::Track}; use crate::tui::{ app::{ machine::{App, AppInner, AppMachine}, selection::ListSelection, - AppPublic, AppState, IAppInteractSearch, + AppPublic, AppState, Category, IAppInteractSearch, }, lib::IMusicHoard, }; @@ -67,7 +67,7 @@ impl IAppInteractSearch for AppMachine { fn append_character(mut self, ch: char) -> Self::APP { 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.incremental_search(false); self.into() @@ -75,7 +75,7 @@ impl IAppInteractSearch for AppMachine { fn search_next(mut self) -> Self::APP { 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.incremental_search(true); } @@ -88,7 +88,7 @@ impl IAppInteractSearch for AppMachine { if memo.char { self.state.string.pop(); } - self.inner.selection.select_artist(collection, memo.index); + self.inner.selection.select(collection, memo.index); } self.into() } @@ -109,12 +109,13 @@ impl IAppInteractSearch for AppMachine { trait IAppInteractSearchPrivate { fn incremental_search(&mut self, next: bool); - fn incremental_search_predicate( - case_sensitive: bool, - char_sensitive: bool, - search_name: &str, - probe: &Artist, - ) -> bool; + fn search_category(list: &[T], index: usize, next: bool, pred: P) -> Option + where + P: FnMut(&T) -> bool; + fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool; + fn predicate_albums(case_sens: bool, char_sens: bool, search: &str, probe: &Album) -> 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_char_sensitive(artist_name: &str) -> bool; @@ -123,50 +124,102 @@ trait IAppInteractSearchPrivate { impl IAppInteractSearchPrivate for AppMachine { fn incremental_search(&mut self, next: bool) { - let artists = self.inner.music_hoard.get_collection(); - let artist_name = &self.state.string; + let collection = self.inner.music_hoard.get_collection(); + let search_name = &self.state.string; let sel = &mut self.inner.selection; - if let Some(mut index) = sel.selected_artist() { - let case_sensitive = Self::is_case_sensitive(artist_name); - let char_sensitive = Self::is_char_sensitive(artist_name); - let search = Self::normalize_search(artist_name, !case_sensitive, !char_sensitive); + if let Some(index) = sel.selected() { + let case_sensitive = Self::is_case_sensitive(search_name); + let char_sensitive = Self::is_char_sensitive(search_name); + let search = Self::normalize_search(search_name, !case_sensitive, !char_sensitive); - if next && ((index + 1) < artists.len()) { - index += 1; - } - let slice = &artists[index..]; + 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) + }) + } + }; - let result = slice.iter().position(|probe| { - Self::incremental_search_predicate(case_sensitive, char_sensitive, &search, probe) - }); - - if let Some(slice_index) = result { - sel.select_artist(artists, Some(index + slice_index)); + if result.is_some() { + sel.select(collection, result); } } } - fn incremental_search_predicate( - case_sensitive: bool, - char_sensitive: bool, - 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); + fn search_category(list: &[T], mut index: usize, next: bool, pred: P) -> Option + where + P: FnMut(&T) -> bool, + { + if next && ((index + 1) < list.len()) { + index += 1; + } + let slice = &list[index..]; + let result = slice.iter().position(pred); + result.map(|slice_index| index + slice_index) + } + + fn predicate_artists(case_sens: bool, char_sens: bool, search: &str, probe: &Artist) -> bool { + let name = Self::normalize_search(&probe.id.name, !case_sens, !char_sens); + let mut result = name.starts_with(search); if let Some(ref probe_sort) = probe.sort { if !result { - let name = - Self::normalize_search(&probe_sort.name, !case_sensitive, !char_sensitive); - result = name.starts_with(search_name); + let name = Self::normalize_search(&probe_sort.name, !case_sens, !char_sens); + result = name.starts_with(search); } } 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 { artist_name .chars() diff --git a/src/tui/app/selection.rs b/src/tui/app/selection.rs index 6c4db7d..34a2845 100644 --- a/src/tui/app/selection.rs +++ b/src/tui/app/selection.rs @@ -80,20 +80,6 @@ impl Selection { self.artist.reinitialise(artists, selected.artist); } - pub fn select_artist(&mut self, artists: &[Artist], index: Option) { - self.artist.select(artists, index); - } - - pub fn selected_artist(&self) -> Option { - 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) { self.active = match self.active { Category::Artist => Category::Album, @@ -110,6 +96,66 @@ impl Selection { }; } + pub fn select(&mut self, collection: &Collection, index: Option) { + 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) { + self.artist.select(artists, index); + } + + fn select_album(&mut self, artists: &[Artist], index: Option) { + self.artist.select_album(artists, index); + } + + fn select_track(&mut self, artists: &[Artist], index: Option) { + self.artist.select_track(artists, index); + } + + pub fn selected(&self) -> Option { + match self.active { + Category::Artist => self.selected_artist(), + Category::Album => self.selected_album(), + Category::Track => self.selected_track(), + } + } + + fn selected_artist(&self) -> Option { + self.artist.selected() + } + + fn selected_album(&self) -> Option { + self.artist.selected_album() + } + + fn selected_track(&self) -> Option { + 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) { match self.active { 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) { match self.active { 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) { 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) { 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) { self.artist.decrement_track(artists, delta); } @@ -197,6 +243,14 @@ impl ArtistSelection { self.state.list.selected() } + fn selected_album(&self) -> Option { + self.album.selected() + } + + fn selected_track(&self) -> Option { + self.album.selected_track() + } + fn select(&mut self, artists: &[Artist], to: Option) { match to { Some(to) => self.select_to(artists, to), @@ -204,6 +258,18 @@ impl ArtistSelection { } } + fn select_album(&mut self, artists: &[Artist], to: Option) { + if let Some(index) = self.state.list.selected() { + self.album.select(&artists[index].albums, to); + } + } + + fn select_track(&mut self, artists: &[Artist], to: Option) { + 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) { to = cmp::min(to, artists.len() - 1); 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) { if let Some(index) = self.state.list.selected() { let result = index.saturating_add(by); @@ -303,6 +387,47 @@ impl AlbumSelection { } } + fn selected(&self) -> Option { + self.state.list.selected() + } + + fn selected_track(&self) -> Option { + self.track.selected() + } + + fn select(&mut self, albums: &[Album], to: Option) { + match to { + Some(to) => self.select_to(albums, to), + None => self.state.list.select(None), + } + } + + fn select_track(&mut self, albums: &[Album], to: Option) { + 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) { if let Some(index) = self.state.list.selected() { let mut result = index.saturating_add(by); @@ -377,6 +502,28 @@ impl TrackSelection { } } + fn selected(&self) -> Option { + self.state.list.selected() + } + + fn select(&mut self, tracks: &[Track], to: Option) { + 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) { if let Some(index) = self.state.list.selected() { let mut result = index.saturating_add(by);