diff --git a/libaegisub/common/util.cpp b/libaegisub/common/util.cpp index 4d3a1544b..a2f349521 100644 --- a/libaegisub/common/util.cpp +++ b/libaegisub/common/util.cpp @@ -52,8 +52,24 @@ std::pair find_range(std::string const& haystack, std::string co return {match_start, match_start + needle.size()}; } +void parse_blocks(std::vector>& blocks, std::string 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; + } } +} // anonymous namespace + namespace agi { namespace util { std::string strftime(const char *fmt, const tm *tmptr) { @@ -129,12 +145,60 @@ std::pair ifind(std::string const& haystack, std::string const& return ret; } +std::string tagless_find_helper::strip_tags(std::string const& str, size_t s) { + parse_blocks(blocks, str); + + std::string 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; } +void tagless_find_helper::map_range(size_t &s, size_t &e) { + 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; + } +} +} // namespace util + #ifndef __APPLE__ namespace osx { AppNapDisabler::AppNapDisabler(std::string reason) { } AppNapDisabler::~AppNapDisabler() { } } #endif -} +} // namespace agi diff --git a/libaegisub/include/libaegisub/util.h b/libaegisub/include/libaegisub/util.h index 3a5cd81e0..4aa34619f 100644 --- a/libaegisub/include/libaegisub/util.h +++ b/libaegisub/include/libaegisub/util.h @@ -15,11 +15,11 @@ #include #include #include +#include struct tm; -namespace agi { - namespace util { +namespace agi { namespace util { /// Clamp `b` to the range [`a`,`c`] template static inline T mid(T a, T b, T c) { @@ -45,6 +45,20 @@ namespace agi { /// based on the unfolded length. std::pair ifind(std::string const& haystack, std::string const& needle); + class tagless_find_helper { + std::vector> blocks; + size_t start = 0; + + public: + /// Strip ASS override tags at or after `start` in `str`, and initialize + /// state for mapping ranges back to the input string + std::string strip_tags(std::string const& str, size_t start); + + /// Convert a range in the string returned by `strip_tags()` to a range + /// int the string last passed to `strip_tags()` + void map_range(size_t& start, size_t& end); + }; + /// Set the name of the calling thread in the Visual Studio debugger /// @param name New name for the thread void SetThreadName(const char *name); @@ -66,6 +80,4 @@ namespace agi { auto range(Integer end) -> decltype(boost::irange(0, end)) { return boost::irange(0, end); } - - } // namespace util -} // namespace agi +} } // namespace agi::util diff --git a/src/search_replace_engine.cpp b/src/search_replace_engine.cpp index d345bfbb1..594c21e5e 100644 --- a/src/search_replace_engine.cpp +++ b/src/search_replace_engine.cpp @@ -73,76 +73,17 @@ public: class skip_tags_accessor { boost::flyweight AssDialogueBase::*field; - std::vector> blocks; - size_t start = 0; - - void parse_str(std::string 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; - } - } + agi::util::tagless_find_helper helper; public: skip_tags_accessor(SearchReplaceSettings::Field f) : field(get_dialogue_field(f)) { } std::string get(const AssDialogue *d, size_t s) { - auto const& str = get_normalized(d, field); - parse_str(str); - - std::string 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; + return helper.strip_tags(get_normalized(d, field), s); } MatchState make_match_state(size_t s, size_t e, boost::u32regex *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; - } + helper.map_range(s, e); return {r, s, e}; } };