From 29f49415dd1cd13261c8c74cf7a519d6fdc620ec Mon Sep 17 00:00:00 2001 From: Andrzej Rybczak Date: Sun, 8 Mar 2009 22:16:18 +0100 Subject: [PATCH] add support for regular expressions (basic and extended) it intruduces regex support in search engine and filtering/searching in all screens --- doc/config | 2 + src/browser.cpp | 5 ++ src/browser.h | 2 +- src/media_library.cpp | 5 ++ src/media_library.h | 2 +- src/menu.h | 47 +++++++++--------- src/ncmpcpp.cpp | 2 +- src/playlist.cpp | 5 ++ src/playlist.h | 2 +- src/playlist_editor.cpp | 5 ++ src/playlist_editor.h | 2 +- src/search_engine.cpp | 103 ++++++++++++++++++++++++++++++---------- src/search_engine.h | 2 +- src/settings.cpp | 5 ++ src/settings.h | 1 + src/song.cpp | 6 ++- src/tag_editor.cpp | 6 +-- 17 files changed, 142 insertions(+), 60 deletions(-) diff --git a/doc/config b/doc/config index 1c392774..ae039b16 100644 --- a/doc/config +++ b/doc/config @@ -144,6 +144,8 @@ # #clock_display_seconds = "no" # +#regular_expressions = "basic" (basic/extended) +# ## ## Note: If below is enabled, ncmpcpp will ignore leading ## "The" word while sorting items in browser, tags in diff --git a/src/browser.cpp b/src/browser.cpp index ed74cf4f..340d186a 100644 --- a/src/browser.cpp +++ b/src/browser.cpp @@ -279,6 +279,11 @@ void Browser::GetSelectedSongs(MPD::SongList &v) } } +void Browser::ApplyFilter(const std::string &s) +{ + w->ApplyFilter(s, itsBrowsedDir == "/" ? 0 : 1, REG_ICASE | Config.regex_type); +} + bool Browser::hasSupportedExtension(const string &file) { size_t last_dot = file.rfind("."); diff --git a/src/browser.h b/src/browser.h index fa5f82ca..69b9cb2f 100644 --- a/src/browser.h +++ b/src/browser.h @@ -44,7 +44,7 @@ class Browser : public Screen< Menu > virtual void ReverseSelection(); virtual void GetSelectedSongs(MPD::SongList &); - virtual void ApplyFilter(const std::string &s) { w->ApplyFilter(s, itsBrowsedDir == "/" ? 0 : 1); } + virtual void ApplyFilter(const std::string &); virtual List *GetList() { return w; } diff --git a/src/media_library.cpp b/src/media_library.cpp index 9296a81b..985cbe1f 100644 --- a/src/media_library.cpp +++ b/src/media_library.cpp @@ -280,6 +280,11 @@ void MediaLibrary::GetSelectedSongs(MPD::SongList &v) } } +void MediaLibrary::ApplyFilter(const std::string &s) +{ + GetList()->ApplyFilter(s, 0, REG_ICASE | Config.regex_type); +} + void MediaLibrary::NextColumn() { if (w == Artists) diff --git a/src/media_library.h b/src/media_library.h index 7eaadd35..5d75f648 100644 --- a/src/media_library.h +++ b/src/media_library.h @@ -53,7 +53,7 @@ class MediaLibrary : public Screen virtual void ReverseSelection() { Songs->ReverseSelection(); } virtual void GetSelectedSongs(MPD::SongList &); - virtual void ApplyFilter(const std::string &s) { GetList()->ApplyFilter(s); } + virtual void ApplyFilter(const std::string &); virtual List *GetList(); diff --git a/src/menu.h b/src/menu.h index 2c73a786..80001a1a 100644 --- a/src/menu.h +++ b/src/menu.h @@ -21,6 +21,7 @@ #ifndef _MENU_H #define _MENU_H +#include #include #include "window.h" @@ -54,12 +55,12 @@ namespace NCurses void ReverseSelection(size_t = 0); bool Deselect(); - virtual bool Search(const std::string &, size_t = 0, bool = 0) = 0; + virtual bool Search(const std::string &, size_t = 0, int = 0) = 0; virtual const std::string &GetSearchConstraint() = 0; virtual void NextFound(bool) = 0; virtual void PrevFound(bool) = 0; - virtual void ApplyFilter(const std::string &, size_t = 0, bool = 0) = 0; + virtual void ApplyFilter(const std::string &, size_t = 0, int = 0) = 0; virtual const std::string &GetFilter() = 0; virtual std::string GetOption(size_t) = 0; @@ -132,12 +133,12 @@ namespace NCurses virtual size_t Choice() const; virtual size_t RealChoice() const; - virtual bool Search(const std::string &constraint, size_t beginning = 0, bool case_sensitive = 0); + virtual bool Search(const std::string &constraint, size_t beginning = 0, int flags = 0); virtual const std::string &GetSearchConstraint() { return itsSearchConstraint; } virtual void NextFound(bool wrap); virtual void PrevFound(bool wrap); - virtual void ApplyFilter(const std::string &filter, size_t beginning = 0, bool case_sensitive = 0); + virtual void ApplyFilter(const std::string &filter, size_t beginning = 0, int flags = 0); virtual const std::string &GetFilter(); virtual std::string GetOption(size_t pos); @@ -596,22 +597,23 @@ template size_t NCurses::Menu::RealChoice() const return result; } -template bool NCurses::Menu::Search(const std::string &constraint, size_t beginning, bool case_sensitive) +template bool NCurses::Menu::Search(const std::string &constraint, size_t beginning, int flags) { itsFound.clear(); itsSearchConstraint.clear(); if (constraint.empty()) return false; itsSearchConstraint = constraint; - std::string option; - for (size_t i = beginning; i < itsOptionsPtr->size(); i++) + regex_t rx; + if (regcomp(&rx, itsSearchConstraint.c_str(), flags) == 0) { - option = GetOption(i); - if (!case_sensitive) - ToLower(option); - if (option.find(itsSearchConstraint) != std::string::npos) - itsFound.insert(i); + for (size_t i = beginning; i < itsOptionsPtr->size(); i++) + { + if (regexec(&rx, GetOption(i).c_str(), 0, 0, 0) == 0) + itsFound.insert(i); + } } + regfree(&rx); return !itsFound.empty(); } @@ -637,15 +639,13 @@ template void NCurses::Menu::PrevFound(bool wrap) Highlight(*itsFound.rbegin()); } -template void NCurses::Menu::ApplyFilter(const std::string &filter, size_t beginning, bool case_sensitive) +template void NCurses::Menu::ApplyFilter(const std::string &filter, size_t beginning, int flags) { if (filter == itsFilter) return; itsFound.clear(); ClearFiltered(); itsFilter = filter; - if (!case_sensitive) - ToLower(itsFilter); if (itsFilter.empty()) return; for (size_t i = 0; i < beginning; i++) @@ -653,18 +653,19 @@ template void NCurses::Menu::ApplyFilter(const std::string &filter, itsFilteredRealPositions.push_back(i); itsFilteredOptions.push_back(itsOptions[i]); } - std::string option; - for (size_t i = beginning; i < itsOptions.size(); i++) + regex_t rx; + if (regcomp(&rx, itsFilter.c_str(), flags) == 0) { - option = GetOption(i); - if (!case_sensitive) - ToLower(option); - if (option.find(itsFilter) != std::string::npos) + for (size_t i = beginning; i < itsOptions.size(); i++) { - itsFilteredRealPositions.push_back(i); - itsFilteredOptions.push_back(itsOptions[i]); + if (regexec(&rx, GetOption(i).c_str(), 0, 0, 0) == 0) + { + itsFilteredRealPositions.push_back(i); + itsFilteredOptions.push_back(itsOptions[i]); + } } } + regfree(&rx); itsOptionsPtr = &itsFilteredOptions; if (itsOptionsPtr->empty()) // oops, we didn't find anything Window::Clear(); diff --git a/src/ncmpcpp.cpp b/src/ncmpcpp.cpp index 8cb34856..e4e3d73d 100644 --- a/src/ncmpcpp.cpp +++ b/src/ncmpcpp.cpp @@ -1493,7 +1493,7 @@ int main(int argc, char *argv[]) if (!findme.empty()) ShowMessage("Searching..."); - bool success = mList->Search(findme, myScreen == mySearcher ? SearchEngine::StaticOptions : 0); + bool success = mList->Search(findme, myScreen == mySearcher ? SearchEngine::StaticOptions : 0, REG_ICASE | Config.regex_type); if (findme.empty()) continue; diff --git a/src/playlist.cpp b/src/playlist.cpp index 1135656c..05c8cc3d 100644 --- a/src/playlist.cpp +++ b/src/playlist.cpp @@ -138,6 +138,11 @@ void Playlist::GetSelectedSongs(MPD::SongList &v) } } +void Playlist::ApplyFilter(const std::string &s) +{ + w->ApplyFilter(s, 0, REG_ICASE | Config.regex_type); +} + void Playlist::Sort() { if (w->GetWidth() < SortDialogWidth || w->GetHeight() < SortDialogHeight) diff --git a/src/playlist.h b/src/playlist.h index a0a61bf0..e0d5b401 100644 --- a/src/playlist.h +++ b/src/playlist.h @@ -48,7 +48,7 @@ class Playlist : public Screen< Menu > virtual void ReverseSelection() { w->ReverseSelection(); } virtual void GetSelectedSongs(MPD::SongList &); - virtual void ApplyFilter(const std::string &s) { w->ApplyFilter(s); } + virtual void ApplyFilter(const std::string &); virtual List *GetList() { return w; } diff --git a/src/playlist_editor.cpp b/src/playlist_editor.cpp index 99c5971b..b93e30d5 100644 --- a/src/playlist_editor.cpp +++ b/src/playlist_editor.cpp @@ -287,6 +287,11 @@ void PlaylistEditor::GetSelectedSongs(MPD::SongList &v) } } +void PlaylistEditor::ApplyFilter(const std::string &s) +{ + GetList()->ApplyFilter(s, 0, REG_ICASE | Config.regex_type); +} + List *PlaylistEditor::GetList() { if (w == Playlists) diff --git a/src/playlist_editor.h b/src/playlist_editor.h index 526705b6..605a3bce 100644 --- a/src/playlist_editor.h +++ b/src/playlist_editor.h @@ -44,7 +44,7 @@ class PlaylistEditor : public Screen virtual void ReverseSelection() { Content->ReverseSelection(); } virtual void GetSelectedSongs(MPD::SongList &); - virtual void ApplyFilter(const std::string &s) { GetList()->ApplyFilter(s); } + virtual void ApplyFilter(const std::string &); virtual List *GetList(); diff --git a/src/search_engine.cpp b/src/search_engine.cpp index 7f5cf43d..b0105ce1 100644 --- a/src/search_engine.cpp +++ b/src/search_engine.cpp @@ -32,7 +32,7 @@ using std::string; SearchEngine *mySearcher = new SearchEngine; -const char *SearchEngine::NormalMode = "Match if tag contains searched phrase"; +const char *SearchEngine::NormalMode = "Match if tag contains searched phrase (regexes supported)"; const char *SearchEngine::StrictMode = "Match only if both values are the same"; size_t SearchEngine::StaticOptions = 20; @@ -307,6 +307,11 @@ void SearchEngine::GetSelectedSongs(MPD::SongList &v) } } +void SearchEngine::ApplyFilter(const std::string &s) +{ + w->ApplyFilter(s, StaticOptions, REG_ICASE | Config.regex_type); +} + void SearchEngine::UpdateFoundList() { bool bold = 0; @@ -392,7 +397,7 @@ void SearchEngine::Search() bool any_found = 1; bool found = 1; - if (!CaseSensitive) + if (!CaseSensitive && !MatchToPattern) { string t; t = s.Any(); @@ -434,9 +439,10 @@ void SearchEngine::Search() for (SongList::const_iterator it = list.begin(); it != list.end(); it++) { + (*it)->CopyPtr(CaseSensitive || MatchToPattern); Song copy = **it; - if (!CaseSensitive) + if (!CaseSensitive && !MatchToPattern) { string t; t = copy.GetArtist(); @@ -471,41 +477,83 @@ void SearchEngine::Search() ToLower(t); copy.SetComment(t); } - else - copy.SetFile(copy.GetName()); if (MatchToPattern) { + regex_t rx; + if (!s.Any().empty()) - any_found = - copy.GetArtist().find(s.Any()) != string::npos - || copy.GetTitle().find(s.Any()) != string::npos - || copy.GetAlbum().find(s.Any()) != string::npos - || copy.GetFile().find(s.Any()) != string::npos - || copy.GetComposer().find(s.Any()) != string::npos - || copy.GetPerformer().find(s.Any()) != string::npos - || copy.GetGenre().find(s.Any()) != string::npos - || copy.GetYear().find(s.Any()) != string::npos - || copy.GetComment().find(s.Any()) != string::npos; + { + if (regcomp(&rx, s.Any().c_str(), ((CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) | Config.regex_type) == 0) + { + any_found = + regexec(&rx, copy.GetArtist().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetTitle().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetAlbum().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetName().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetComposer().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetPerformer().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetGenre().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetYear().c_str(), 0, 0, 0) == 0 + || regexec(&rx, copy.GetComment().c_str(), 0, 0, 0) == 0; + } + regfree(&rx); + } if (found && !s.GetArtist().empty()) - found = copy.GetArtist().find(s.GetArtist()) != string::npos; + { + if (regcomp(&rx, s.GetArtist().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetArtist().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetTitle().empty()) - found = copy.GetTitle().find(s.GetTitle()) != string::npos; + { + if (regcomp(&rx, s.GetTitle().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetTitle().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetAlbum().empty()) - found = copy.GetAlbum().find(s.GetAlbum()) != string::npos; + { + if (regcomp(&rx, s.GetAlbum().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetAlbum().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetFile().empty()) - found = copy.GetFile().find(s.GetFile()) != string::npos; + { + if (regcomp(&rx, s.GetFile().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetName().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetComposer().empty()) - found = copy.GetComposer().find(s.GetComposer()) != string::npos; + { + if (regcomp(&rx, s.GetComposer().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetComposer().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetPerformer().empty()) - found = copy.GetPerformer().find(s.GetPerformer()) != string::npos; + { + if (regcomp(&rx, s.GetPerformer().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetPerformer().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetGenre().empty()) - found = copy.GetGenre().find(s.GetGenre()) != string::npos; + { + if (regcomp(&rx, s.GetGenre().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetGenre().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetYear().empty()) - found = copy.GetYear().find(s.GetYear()) != string::npos; + { + if (regcomp(&rx, s.GetYear().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetYear().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } if (found && !s.GetComment().empty()) - found = copy.GetComment().find(s.GetComment()) != string::npos; + { + if (regcomp(&rx, s.GetComment().c_str(), (CaseSensitive ? 0 : REG_ICASE) | Config.regex_type) == 0) + found = regexec(&rx, copy.GetComment().c_str(), 0, 0, 0) == 0; + regfree(&rx); + } } else { @@ -514,7 +562,7 @@ void SearchEngine::Search() copy.GetArtist() == s.Any() || copy.GetTitle() == s.Any() || copy.GetAlbum() == s.Any() - || copy.GetFile() == s.Any() + || copy.GetName() == s.Any() || copy.GetComposer() == s.Any() || copy.GetPerformer() == s.Any() || copy.GetGenre() == s.Any() @@ -528,7 +576,7 @@ void SearchEngine::Search() if (found && !s.GetAlbum().empty()) found = copy.GetAlbum() == s.GetAlbum(); if (found && !s.GetFile().empty()) - found = copy.GetFile() == s.GetFile(); + found = copy.GetName() == s.GetFile(); if (found && !s.GetComposer().empty()) found = copy.GetComposer() == s.GetComposer(); if (found && !s.GetPerformer().empty()) @@ -541,6 +589,9 @@ void SearchEngine::Search() found = copy.GetComment() == s.GetComment(); } + copy.NullMe(); + (*it)->CopyPtr(0); + if (found && any_found) { Song *ss = Config.search_in_db ? *it : new Song(**it); diff --git a/src/search_engine.h b/src/search_engine.h index 5bd48395..67410d76 100644 --- a/src/search_engine.h +++ b/src/search_engine.h @@ -55,7 +55,7 @@ class SearchEngine : public Screen< Menu< std::pair > > virtual void ReverseSelection() { w->ReverseSelection(StaticOptions); } virtual void GetSelectedSongs(MPD::SongList &); - virtual void ApplyFilter(const std::string &s) { w->ApplyFilter(s, StaticOptions); } + virtual void ApplyFilter(const std::string &); virtual List *GetList() { return w->Size() >= StaticOptions ? w : 0; } diff --git a/src/settings.cpp b/src/settings.cpp index ccca7bdf..7e93c39a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -276,6 +276,7 @@ void DefaultConfiguration(ncmpcpp_config &conf) conf.playlist_disable_highlight_delay = 5; conf.message_delay_time = 4; conf.lyrics_db = 0; + conf.regex_type = 0; } void ReadKeys(ncmpcpp_keys &keys) @@ -646,6 +647,10 @@ void ReadConfiguration(ncmpcpp_config &conf) { conf.set_window_title = v == "yes"; } + else if (cl.find("regular_expressions") != string::npos) + { + conf.regex_type = REG_EXTENDED * (v != "basic"); + } else if (cl.find("lyrics_database") != string::npos) { if (!v.empty()) diff --git a/src/settings.h b/src/settings.h index a52ab715..10f892be 100644 --- a/src/settings.h +++ b/src/settings.h @@ -168,6 +168,7 @@ struct ncmpcpp_config int playlist_disable_highlight_delay; int message_delay_time; int lyrics_db; + int regex_type; }; extern ncmpcpp_config Config; diff --git a/src/song.cpp b/src/song.cpp index 21b75512..26d21e18 100644 --- a/src/song.cpp +++ b/src/song.cpp @@ -198,6 +198,7 @@ void Song::SetFile(const string &str) if (itsSong->file) str_pool_put(itsSong->file); itsSong->file = str.empty() ? 0 : str_pool_get(str.c_str()); + CountLastSlashPosition(); } void Song::SetArtist(const string &str) @@ -544,8 +545,9 @@ string Song::ShowTime(int length) void Song::CountLastSlashPosition() { + if (!itsSong->file) + return; char *tmp = strrchr(itsSong->file, '/'); - if (tmp) - itsSlash = tmp-itsSong->file; + itsSlash = tmp ? tmp-itsSong->file : string::npos; } diff --git a/src/tag_editor.cpp b/src/tag_editor.cpp index 8fc6ab59..bcf6306c 100644 --- a/src/tag_editor.cpp +++ b/src/tag_editor.cpp @@ -741,11 +741,11 @@ void TagEditor::GetSelectedSongs(MPD::SongList &v) void TagEditor::ApplyFilter(const std::string &s) { if (w == Dirs) - Dirs->ApplyFilter(s, 1); + Dirs->ApplyFilter(s, 1, REG_ICASE | Config.regex_type); else if (w == Albums) - Albums->ApplyFilter(s); + Albums->ApplyFilter(s, 0, REG_ICASE | Config.regex_type); else if (w == Tags) - Tags->ApplyFilter(s); + Tags->ApplyFilter(s, 0, REG_ICASE | Config.regex_type); } List *TagEditor::GetList()