forked from mia/Aegisub
Add an option to skip override tags when searching. Closes #104.
This commit is contained in:
parent
5fff88473f
commit
3b4d121d4a
5 changed files with 130 additions and 21 deletions
|
@ -57,6 +57,7 @@ DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool replace)
|
||||||
settings->match_case = OPT_GET("Tool/Search Replace/Match Case")->GetBool();
|
settings->match_case = OPT_GET("Tool/Search Replace/Match Case")->GetBool();
|
||||||
settings->use_regex = OPT_GET("Tool/Search Replace/RegExp")->GetBool();
|
settings->use_regex = OPT_GET("Tool/Search Replace/RegExp")->GetBool();
|
||||||
settings->ignore_comments = OPT_GET("Tool/Search Replace/Skip Comments")->GetBool();
|
settings->ignore_comments = OPT_GET("Tool/Search Replace/Skip Comments")->GetBool();
|
||||||
|
settings->skip_tags = OPT_GET("Tool/Search Replace/Skip Tags")->GetBool();
|
||||||
|
|
||||||
auto find_sizer = new wxFlexGridSizer(2, 2, 5, 15);
|
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));
|
find_edit = new wxComboBox(this, -1, "", wxDefaultPosition, wxSize(300, -1), recent_find, wxCB_DROPDOWN, wxGenericValidator(&settings->find));
|
||||||
|
@ -72,7 +73,8 @@ DialogSearchReplace::DialogSearchReplace(agi::Context* c, bool replace)
|
||||||
auto options_sizer = new wxBoxSizer(wxVERTICAL);
|
auto options_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
options_sizer->Add(new wxCheckBox(this, -1, _("&Match case"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->match_case)), wxSizerFlags().Border(wxBOTTOM));
|
options_sizer->Add(new wxCheckBox(this, -1, _("&Match case"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->match_case)), wxSizerFlags().Border(wxBOTTOM));
|
||||||
options_sizer->Add(new wxCheckBox(this, -1, _("&Use regular expressions"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->use_regex)), wxSizerFlags().Border(wxBOTTOM));
|
options_sizer->Add(new wxCheckBox(this, -1, _("&Use regular expressions"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->use_regex)), wxSizerFlags().Border(wxBOTTOM));
|
||||||
options_sizer->Add(new wxCheckBox(this, -1, _("&Skip Comments"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->ignore_comments)));
|
options_sizer->Add(new wxCheckBox(this, -1, _("&Skip Comments"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->ignore_comments)), wxSizerFlags().Border(wxBOTTOM));
|
||||||
|
options_sizer->Add(new wxCheckBox(this, -1, _("S&kip Override Tags"), wxDefaultPosition, wxDefaultSize, 0, wxGenericValidator(&settings->skip_tags)));
|
||||||
|
|
||||||
auto left_sizer = new wxBoxSizer(wxVERTICAL);
|
auto left_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
left_sizer->Add(find_sizer, wxSizerFlags().DoubleBorder(wxBOTTOM));
|
left_sizer->Add(find_sizer, wxSizerFlags().DoubleBorder(wxBOTTOM));
|
||||||
|
@ -134,6 +136,7 @@ void DialogSearchReplace::FindReplace(bool (SearchReplaceEngine::*func)()) {
|
||||||
OPT_SET("Tool/Search Replace/Match Case")->SetBool(settings->match_case);
|
OPT_SET("Tool/Search Replace/Match Case")->SetBool(settings->match_case);
|
||||||
OPT_SET("Tool/Search Replace/RegExp")->SetBool(settings->use_regex);
|
OPT_SET("Tool/Search Replace/RegExp")->SetBool(settings->use_regex);
|
||||||
OPT_SET("Tool/Search Replace/Skip Comments")->SetBool(settings->ignore_comments);
|
OPT_SET("Tool/Search Replace/Skip Comments")->SetBool(settings->ignore_comments);
|
||||||
|
OPT_SET("Tool/Search Replace/Skip Tags")->SetBool(settings->skip_tags);
|
||||||
OPT_SET("Tool/Search Replace/Field")->SetInt(static_cast<int>(settings->field));
|
OPT_SET("Tool/Search Replace/Field")->SetInt(static_cast<int>(settings->field));
|
||||||
OPT_SET("Tool/Search Replace/Affect")->SetInt(static_cast<int>(settings->limit_to));
|
OPT_SET("Tool/Search Replace/Affect")->SetInt(static_cast<int>(settings->limit_to));
|
||||||
|
|
||||||
|
|
|
@ -463,7 +463,8 @@
|
||||||
"Field" : 0,
|
"Field" : 0,
|
||||||
"Match Case" : false,
|
"Match Case" : false,
|
||||||
"RegExp" : false,
|
"RegExp" : false,
|
||||||
"Skip Comments" : false
|
"Skip Comments" : false,
|
||||||
|
"Skip Tags" : false
|
||||||
},
|
},
|
||||||
"Select Lines" : {
|
"Select Lines" : {
|
||||||
"Action" : 0,
|
"Action" : 0,
|
||||||
|
|
|
@ -463,7 +463,8 @@
|
||||||
"Field" : 0,
|
"Field" : 0,
|
||||||
"Match Case" : false,
|
"Match Case" : false,
|
||||||
"RegExp" : false,
|
"RegExp" : false,
|
||||||
"Skip Comments" : false
|
"Skip Comments" : false,
|
||||||
|
"Skip Tags" : false
|
||||||
},
|
},
|
||||||
"Select Lines" : {
|
"Select Lines" : {
|
||||||
"Action" : 0,
|
"Action" : 0,
|
||||||
|
|
|
@ -29,13 +29,15 @@
|
||||||
#include <wx/msgdlg.h>
|
#include <wx/msgdlg.h>
|
||||||
#include <wx/regex.h>
|
#include <wx/regex.h>
|
||||||
|
|
||||||
|
static const size_t bad_pos = -1;
|
||||||
|
|
||||||
struct MatchState {
|
struct MatchState {
|
||||||
wxRegEx *re;
|
wxRegEx *re;
|
||||||
size_t start, end;
|
size_t start, end;
|
||||||
|
|
||||||
MatchState() : re(nullptr), start(0), end(-1) { }
|
MatchState() : re(nullptr), start(0), end(-1) { }
|
||||||
MatchState(size_t s, size_t e, wxRegEx *re = nullptr) : re(re), start(s), end(e) { }
|
MatchState(size_t s, size_t e, wxRegEx *re) : re(re), start(s), end(e) { }
|
||||||
operator bool() { return end != -1; }
|
operator bool() { return end != bad_pos; }
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -50,32 +52,123 @@ auto get_dialogue_field(AssDialogue *cur, SearchReplaceSettings::Field field) ->
|
||||||
throw agi::InternalError("Bad field for search", 0);
|
throw agi::InternalError("Bad field for search", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::function<MatchState (const AssDialogue*, size_t)> get_matcher(SearchReplaceSettings::Field field, wxString look_for, bool use_regex, bool match_case, wxRegEx *regex) {
|
typedef std::function<MatchState (const AssDialogue*, size_t)> matcher;
|
||||||
if (use_regex) {
|
|
||||||
|
struct noop_accessor {
|
||||||
|
SearchReplaceSettings::Field field;
|
||||||
|
size_t start;
|
||||||
|
|
||||||
|
wxString get(const AssDialogue *d, size_t s) {
|
||||||
|
start = s;
|
||||||
|
return get_dialogue_field(d, field)->get().substr(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchState make_match_state(size_t s, size_t e, wxRegEx *r = nullptr) {
|
||||||
|
return MatchState(s + start, e + start, r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct skip_tags_accessor {
|
||||||
|
SearchReplaceSettings::Field field;
|
||||||
|
std::vector<std::pair<size_t, size_t>> blocks;
|
||||||
|
size_t start;
|
||||||
|
|
||||||
|
void parse_str(wxString const& str) {
|
||||||
|
blocks.clear();
|
||||||
|
|
||||||
|
size_t ovr_start = bad_pos;
|
||||||
|
size_t i = 0;
|
||||||
|
for (auto const& c : str) {
|
||||||
|
if (c == '{' && ovr_start == bad_pos)
|
||||||
|
ovr_start = i;
|
||||||
|
else if (c == '}' && ovr_start != bad_pos) {
|
||||||
|
blocks.emplace_back(ovr_start, i);
|
||||||
|
ovr_start = bad_pos;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString get(const AssDialogue *d, size_t s) {
|
||||||
|
auto const& str = get_dialogue_field(d, field)->get();
|
||||||
|
parse_str(str);
|
||||||
|
|
||||||
|
wxString out;
|
||||||
|
|
||||||
|
size_t last = s;
|
||||||
|
for (auto const& block : blocks) {
|
||||||
|
if (block.second < s) continue;
|
||||||
|
if (block.first > last)
|
||||||
|
out.append(str.begin() + last, str.begin() + block.first);
|
||||||
|
last = block.second + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last < str.size())
|
||||||
|
out.append(str.begin() + last, str.end());
|
||||||
|
|
||||||
|
start = s;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatchState make_match_state(size_t s, size_t e, wxRegEx *r = nullptr) {
|
||||||
|
s += start;
|
||||||
|
e += start;
|
||||||
|
|
||||||
|
// Shift the start and end of the match to be relative to the unstripped
|
||||||
|
// match
|
||||||
|
for (auto const& block : blocks) {
|
||||||
|
// Any blocks before start are irrelevant as they're included in `start`
|
||||||
|
if (block.second < s) continue;
|
||||||
|
// Skip over blocks at the very beginning of the match
|
||||||
|
// < should only happen if the cursor was within an override block
|
||||||
|
// when the user started a search
|
||||||
|
if (block.first <= s) {
|
||||||
|
size_t len = block.second - std::max(block.first, s) + 1;
|
||||||
|
s += len;
|
||||||
|
e += len;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(block.first > s);
|
||||||
|
// Blocks after the match are irrelevant
|
||||||
|
if (block.first >= e) break;
|
||||||
|
|
||||||
|
// Extend the match to include blocks within the match
|
||||||
|
// Note that blocks cannot be partially within the match
|
||||||
|
e += block.second - block.first + 1;
|
||||||
|
}
|
||||||
|
return MatchState(s, e, r);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Accessor>
|
||||||
|
matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex, Accessor a) {
|
||||||
|
if (settings.use_regex) {
|
||||||
int flags = wxRE_ADVANCED;
|
int flags = wxRE_ADVANCED;
|
||||||
if (!match_case)
|
if (!settings.match_case)
|
||||||
flags |= wxRE_ICASE;
|
flags |= wxRE_ICASE;
|
||||||
|
|
||||||
regex->Compile(look_for, flags);
|
regex->Compile(settings.find, flags);
|
||||||
if (!regex->IsValid())
|
if (!regex->IsValid())
|
||||||
return [](const AssDialogue*, size_t) { return MatchState(); };
|
return [](const AssDialogue*, size_t) { return MatchState(); };
|
||||||
|
|
||||||
return [=](const AssDialogue *diag, size_t start) {
|
return [=](const AssDialogue *diag, size_t start) mutable -> MatchState {
|
||||||
auto const& str = *get_dialogue_field(diag, field);
|
if (!regex->Matches(a.get(diag, start)))
|
||||||
if (!regex->Matches(str.get().substr(start)))
|
|
||||||
return MatchState();
|
return MatchState();
|
||||||
|
|
||||||
size_t match_start, match_len;
|
size_t match_start, match_len;
|
||||||
regex->GetMatch(&match_start, &match_len, 0);
|
regex->GetMatch(&match_start, &match_len, 0);
|
||||||
return MatchState(match_start + start, match_start + match_len + start, regex);
|
return a.make_match_state(match_start, match_start + match_len, regex);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!match_case)
|
bool match_case = settings.match_case;
|
||||||
|
wxString look_for = settings.find;
|
||||||
|
if (!settings.match_case)
|
||||||
look_for.MakeLower();
|
look_for.MakeLower();
|
||||||
|
|
||||||
return [=](const AssDialogue *diag, size_t start) {
|
return [=](const AssDialogue *diag, size_t start) mutable -> MatchState {
|
||||||
auto str = get_dialogue_field(diag, field)->get().substr(start);
|
auto str = a.get(diag, start);
|
||||||
if (!match_case)
|
if (!match_case)
|
||||||
str.MakeLower();
|
str.MakeLower();
|
||||||
|
|
||||||
|
@ -83,10 +176,20 @@ std::function<MatchState (const AssDialogue*, size_t)> get_matcher(SearchReplace
|
||||||
if (pos == wxString::npos)
|
if (pos == wxString::npos)
|
||||||
return MatchState();
|
return MatchState();
|
||||||
|
|
||||||
return MatchState(pos + start, pos + look_for.size() + start);
|
return a.make_match_state(pos, pos + look_for.size());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matcher get_matcher(SearchReplaceSettings const& settings, wxRegEx *regex) {
|
||||||
|
if (!settings.skip_tags) {
|
||||||
|
noop_accessor a = { settings.field };
|
||||||
|
return get_matcher(settings, regex, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_tags_accessor a = { settings.field };
|
||||||
|
return get_matcher(settings, regex, a);
|
||||||
|
}
|
||||||
|
|
||||||
template<typename Iterator, typename Container>
|
template<typename Iterator, typename Container>
|
||||||
Iterator circular_next(Iterator it, Container& c) {
|
Iterator circular_next(Iterator it, Container& c) {
|
||||||
++it;
|
++it;
|
||||||
|
@ -123,7 +226,7 @@ bool SearchReplaceEngine::FindReplace(bool replace) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
wxRegEx r;
|
wxRegEx r;
|
||||||
auto matches = get_matcher(settings.field, settings.find, settings.use_regex, settings.match_case, &r);
|
auto matches = get_matcher(settings, &r);
|
||||||
|
|
||||||
AssDialogue *line = context->selectionController->GetActiveLine();
|
AssDialogue *line = context->selectionController->GetActiveLine();
|
||||||
auto it = context->ass->Line.iterator_to(*line);
|
auto it = context->ass->Line.iterator_to(*line);
|
||||||
|
@ -135,11 +238,11 @@ bool SearchReplaceEngine::FindReplace(bool replace) {
|
||||||
pos = context->textSelectionController->GetSelectionStart();
|
pos = context->textSelectionController->GetSelectionStart();
|
||||||
|
|
||||||
if ((replace_ms = matches(line, pos))) {
|
if ((replace_ms = matches(line, pos))) {
|
||||||
size_t end = -1;
|
size_t end = bad_pos;
|
||||||
if (settings.field == SearchReplaceSettings::Field::TEXT)
|
if (settings.field == SearchReplaceSettings::Field::TEXT)
|
||||||
end = context->textSelectionController->GetSelectionEnd();
|
end = context->textSelectionController->GetSelectionEnd();
|
||||||
|
|
||||||
if (end != -1 || (pos == replace_ms.start && end == replace_ms.end)) {
|
if (end == bad_pos || (pos == replace_ms.start && end == replace_ms.end)) {
|
||||||
Replace(line, replace_ms);
|
Replace(line, replace_ms);
|
||||||
pos = replace_ms.end;
|
pos = replace_ms.end;
|
||||||
context->ass->Commit(_("replace"), AssFile::COMMIT_DIAG_TEXT);
|
context->ass->Commit(_("replace"), AssFile::COMMIT_DIAG_TEXT);
|
||||||
|
@ -202,7 +305,7 @@ bool SearchReplaceEngine::ReplaceAll() {
|
||||||
size_t count = 0;
|
size_t count = 0;
|
||||||
|
|
||||||
wxRegEx r;
|
wxRegEx r;
|
||||||
auto matches = get_matcher(settings.field, settings.find, settings.use_regex, settings.match_case, &r);
|
auto matches = get_matcher(settings, &r);
|
||||||
|
|
||||||
SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
|
SubtitleSelection const& sel = context->selectionController->GetSelectedSet();
|
||||||
bool selection_only = settings.limit_to == SearchReplaceSettings::Limit::SELECTED;
|
bool selection_only = settings.limit_to == SearchReplaceSettings::Limit::SELECTED;
|
||||||
|
|
|
@ -42,6 +42,7 @@ struct SearchReplaceSettings {
|
||||||
bool match_case;
|
bool match_case;
|
||||||
bool use_regex;
|
bool use_regex;
|
||||||
bool ignore_comments;
|
bool ignore_comments;
|
||||||
|
bool skip_tags;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SearchReplaceEngine {
|
class SearchReplaceEngine {
|
||||||
|
|
Loading…
Reference in a new issue