diff --git a/aegisub/src/dialog_search_replace.cpp b/aegisub/src/dialog_search_replace.cpp index 4e6e5cf4c..2f1aaf1db 100644 --- a/aegisub/src/dialog_search_replace.cpp +++ b/aegisub/src/dialog_search_replace.cpp @@ -58,6 +58,7 @@ DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool replace) settings->use_regex = OPT_GET("Tool/Search Replace/RegExp")->GetBool(); settings->ignore_comments = OPT_GET("Tool/Search Replace/Skip Comments")->GetBool(); settings->skip_tags = OPT_GET("Tool/Search Replace/Skip Tags")->GetBool(); + settings->exact_match = false; auto find_sizer = new wxFlexGridSizer(2, 2, 5, 15); find_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), recent_find, wxCB_DROPDOWN, wxGenericValidator(&settings->find)); diff --git a/aegisub/src/dialog_selection.cpp b/aegisub/src/dialog_selection.cpp index 6b1eb7b9c..46fc778db 100644 --- a/aegisub/src/dialog_selection.cpp +++ b/aegisub/src/dialog_selection.cpp @@ -32,6 +32,7 @@ #include "libresrc/libresrc.h" #include "main.h" #include "options.h" +#include "search_replace_engine.h" #include "selection_controller.h" #include @@ -53,72 +54,35 @@ enum { ACTION_INTERSECT }; -enum { - FIELD_TEXT = 0, - FIELD_STYLE, - FIELD_ACTOR, - FIELD_EFFECT -}; - enum { MODE_EXACT = 0, MODE_CONTAINS, MODE_REGEXP }; -DEFINE_SIMPLE_EXCEPTION(BadRegex, agi::InvalidInputException, "bad_regex") - -static boost::flyweight AssDialogue::* get_field(int field_n) { - switch(field_n) { - case FIELD_TEXT: return &AssDialogue::Text; break; - case FIELD_STYLE: return &AssDialogue::Style; break; - case FIELD_ACTOR: return &AssDialogue::Actor; break; - case FIELD_EFFECT: return &AssDialogue::Effect; break; - default: throw agi::InternalError("Bad field", 0); - } -} - -std::function get_predicate(int mode, wxRegEx *re, bool match_case, wxString const& match_text) { - using std::placeholders::_1; - - switch (mode) { - case MODE_REGEXP: - return [=](wxString str) { return re->Matches(str); }; - case MODE_EXACT: - if (match_case) - return std::bind(std::equal_to(), match_text, _1); - else - return bind(std::equal_to(), match_text.Lower(), std::bind(&wxString::Lower, _1)); - case MODE_CONTAINS: - if (match_case) - return std::bind(&wxString::Contains, _1, match_text); - else - return bind(&wxString::Contains, std::bind(&wxString::Lower, _1), match_text.Lower()); - break; - default: throw agi::InternalError("Bad mode", 0); - } -} - -static std::set process(wxString match_text, bool match_case, int mode, bool invert, bool comments, bool dialogue, int field_n, AssFile *ass) { +static std::set process(wxString const& match_text, bool match_case, int mode, bool invert, bool comments, bool dialogue, int field_n, AssFile *ass) { wxRegEx re; - if (mode == MODE_REGEXP) { - int flags = wxRE_ADVANCED; - if (!match_case) - flags |= wxRE_ICASE; - if (!re.Compile(match_text, flags)) - throw BadRegex("Syntax error in regular expression", 0); - match_case = false; - } - boost::flyweight AssDialogue::*field = get_field(field_n); - std::function pred = get_predicate(mode, &re, match_case, match_text); + SearchReplaceSettings settings = { + match_text, + wxString(), + static_cast(field_n), + SearchReplaceSettings::Limit::ALL, + match_case, + mode == MODE_REGEXP, + false, + false, + mode == MODE_EXACT + }; + + auto predicate = SearchReplaceEngine::GetMatcher(settings, &re); std::set matches; for (auto diag : ass->Line | agi::of_type()) { if (diag->Comment && !comments) continue; if (!diag->Comment && !dialogue) continue; - if (pred(diag->*field) != invert) + if (invert != predicate(diag, 0)) matches.insert(diag); } diff --git a/aegisub/src/search_replace_engine.cpp b/aegisub/src/search_replace_engine.cpp index b934263f7..ba72e494e 100644 --- a/aegisub/src/search_replace_engine.cpp +++ b/aegisub/src/search_replace_engine.cpp @@ -31,16 +31,9 @@ static const size_t bad_pos = -1; -struct MatchState { - wxRegEx *re; - size_t start, end; - - MatchState() : re(nullptr), start(0), end(-1) { } - MatchState(size_t s, size_t e, wxRegEx *re) : re(re), start(s), end(e) { } - operator bool() { return end != bad_pos; } -}; - namespace { +DEFINE_SIMPLE_EXCEPTION(BadRegex, agi::InvalidInputException, "bad_regex") + auto get_dialogue_field(SearchReplaceSettings::Field field) -> decltype(&AssDialogue::Text) { switch (field) { case SearchReplaceSettings::Field::TEXT: return &AssDialogue::Text; @@ -147,7 +140,7 @@ public: }; template -matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Accessor a) { +matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Accessor&& a) { if (settings.use_regex) { int flags = wxRE_ADVANCED; if (!settings.match_case) @@ -155,7 +148,7 @@ matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Acces regex->Compile(settings.find, flags); if (!regex->IsValid()) - return [](const AssDialogue*, size_t) { return MatchState(); }; + throw BadRegex("Invalid syntax in regular expression", nullptr); return [=](const AssDialogue *diag, size_t start) mutable -> MatchState { if (!regex->Matches(a.get(diag, start))) @@ -167,6 +160,7 @@ matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Acces }; } + bool full_match_only = settings.exact_match; bool match_case = settings.match_case; wxString look_for = settings.find; if (!settings.match_case) @@ -174,6 +168,9 @@ matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Acces return [=](const AssDialogue *diag, size_t start) mutable -> MatchState { auto str = a.get(diag, start); + if (full_match_only && str.size() != look_for.size()) + return MatchState(); + if (!match_case) str.MakeLower(); @@ -185,12 +182,6 @@ matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Acces }; } -matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex) { - if (settings.skip_tags) - return get_matcher(settings, regex, skip_tags_accessor(settings.field)); - return get_matcher(settings, regex, noop_accessor(settings.field)); -} - template Iterator circular_next(Iterator it, Container& c) { ++it; @@ -201,6 +192,12 @@ Iterator circular_next(Iterator it, Container& c) { } +std::function SearchReplaceEngine::GetMatcher(SearchReplaceSettings const& settings, wxRegEx *regex) { + if (settings.skip_tags) + return get_matcher(settings, regex, skip_tags_accessor(settings.field)); + return get_matcher(settings, regex, noop_accessor(settings.field)); +} + SearchReplaceEngine::SearchReplaceEngine(agi::Context *c) : context(c) , initialized(false) @@ -227,7 +224,7 @@ bool SearchReplaceEngine::FindReplace(bool replace) { return false; wxRegEx r; - auto matches = get_matcher(settings, &r); + auto matches = GetMatcher(settings, &r); AssDialogue *line = context->selectionController->GetActiveLine(); auto it = context->ass->Line.iterator_to(*line); @@ -306,7 +303,7 @@ bool SearchReplaceEngine::ReplaceAll() { size_t count = 0; wxRegEx r; - auto matches = get_matcher(settings, &r); + auto matches = GetMatcher(settings, &r); SubtitleSelection const& sel = context->selectionController->GetSelectedSet(); bool selection_only = settings.limit_to == SearchReplaceSettings::Limit::SELECTED; diff --git a/aegisub/src/search_replace_engine.h b/aegisub/src/search_replace_engine.h index 11e7fe76c..95c9ad1c3 100644 --- a/aegisub/src/search_replace_engine.h +++ b/aegisub/src/search_replace_engine.h @@ -14,11 +14,21 @@ // // Aegisub Project http://www.aegisub.org/ +#include #include namespace agi { struct Context; } class AssDialogue; -struct MatchState; +class wxRegEx; + +struct MatchState { + wxRegEx *re; + size_t start, end; + + MatchState() : re(nullptr), start(0), end(-1) { } + MatchState(size_t s, size_t e, wxRegEx *re) : re(re), start(s), end(e) { } + operator bool() const { return end != (size_t)-1; } +}; struct SearchReplaceSettings { enum class Field { @@ -43,6 +53,7 @@ struct SearchReplaceSettings { bool use_regex; bool ignore_comments; bool skip_tags; + bool exact_match; }; class SearchReplaceEngine { @@ -60,5 +71,7 @@ public: void Configure(SearchReplaceSettings const& new_settings); + static std::function GetMatcher(SearchReplaceSettings const& settings, wxRegEx *regex); + SearchReplaceEngine(agi::Context *c); };