diff --git a/src/modules/powerrename/lib/PowerRenameRegEx.cpp b/src/modules/powerrename/lib/PowerRenameRegEx.cpp index 76136ff39c..34d3cc5c0c 100644 --- a/src/modules/powerrename/lib/PowerRenameRegEx.cpp +++ b/src/modules/powerrename/lib/PowerRenameRegEx.cpp @@ -154,8 +154,7 @@ HRESULT CPowerRenameRegEx::_OnEnumerateOrRandomizeItemsChanged() std::find_if( m_randomizer.begin(), m_randomizer.end(), - [option](const Randomizer& r) -> bool { return r.options.replaceStrSpan.offset == option.replaceStrSpan.offset; } - )) + [option](const Randomizer& r) -> bool { return r.options.replaceStrSpan.offset == option.replaceStrSpan.offset; })) { // Only add as enumerator if we didn't find a randomizer already at this offset. // Every randomizer will also be a valid enumerator according to the definition of enumerators, which allows any string to mean the default enumerator, so it should be interpreted that the user wanted a randomizer if both were found at the same offset of the replace string. @@ -395,11 +394,8 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result, u } std::wstring sourceToUse; - std::wstring originalSource; sourceToUse.reserve(MAX_PATH); - originalSource.reserve(MAX_PATH); sourceToUse = source; - originalSource = sourceToUse; std::wstring searchTerm(m_searchTerm); std::wstring replaceTerm; @@ -487,27 +483,46 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result, u } } - bool replacedSomething = false; + bool shouldIncrementCounter = false; + const bool isCaseInsensitive = !(m_flags & CaseSensitive); + if (m_flags & UseRegularExpressions) { replaceTerm = regex_replace(replaceTerm, zeroGroupRegex, L"$1$$$0"); replaceTerm = regex_replace(replaceTerm, otherGroupsRegex, L"$1$0$4"); - res = RegexReplaceDispatch[_useBoostLib](source, m_searchTerm, replaceTerm, m_flags & MatchAllOccurrences, !(m_flags & CaseSensitive)); - replacedSomething = originalSource != res; + res = RegexReplaceDispatch[_useBoostLib](source, m_searchTerm, replaceTerm, m_flags & MatchAllOccurrences, isCaseInsensitive); + + // Use regex search to determine if a match exists. This is the basis for incrementing + // the counter. + if (_useBoostLib) + { + boost::wregex pattern(m_searchTerm, boost::wregex::ECMAScript | (isCaseInsensitive ? boost::wregex::icase : boost::wregex::normal)); + shouldIncrementCounter = boost::regex_search(sourceToUse, pattern); + } + else + { + auto regexFlags = std::wregex::ECMAScript; + if (isCaseInsensitive) + { + regexFlags |= std::wregex::icase; + } + std::wregex pattern(m_searchTerm, regexFlags); + shouldIncrementCounter = std::regex_search(sourceToUse, pattern); + } } else { - // Simple search and replace + // Simple search and replace. size_t pos = 0; do { - pos = _Find(sourceToUse, searchTerm, (!(m_flags & CaseSensitive)), pos); + pos = _Find(sourceToUse, searchTerm, isCaseInsensitive, pos); if (pos != std::string::npos) { res = sourceToUse.replace(pos, searchTerm.length(), replaceTerm); pos += replaceTerm.length(); - replacedSomething = true; + shouldIncrementCounter = true; } if (!(m_flags & MatchAllOccurrences)) { @@ -516,7 +531,8 @@ HRESULT CPowerRenameRegEx::Replace(_In_ PCWSTR source, _Outptr_ PWSTR* result, u } while (pos != std::string::npos); } hr = SHStrDup(res.c_str(), result); - if (replacedSomething) + + if (shouldIncrementCounter) enumIndex++; } catch (regex_error e) diff --git a/src/modules/powerrename/unittests/CommonRegExTests.h b/src/modules/powerrename/unittests/CommonRegExTests.h index b1b2b8d731..1b0ad30b92 100644 --- a/src/modules/powerrename/unittests/CommonRegExTests.h +++ b/src/modules/powerrename/unittests/CommonRegExTests.h @@ -611,6 +611,42 @@ TEST_METHOD (VerifyRandomizerRegExAllBackToBack) CoTaskMemFree(result); } +TEST_METHOD(VerifyCounterIncrementsWhenResultIsUnchanged) +{ + CComPtr renameRegEx; + Assert::IsTrue(CPowerRenameRegEx::s_CreateInstance(&renameRegEx) == S_OK); + DWORD flags = EnumerateItems | UseRegularExpressions; + Assert::IsTrue(renameRegEx->PutFlags(flags) == S_OK); + + renameRegEx->PutSearchTerm(L"(.*)"); + renameRegEx->PutReplaceTerm(L"NewFile-${start=1}"); + + PWSTR result = nullptr; + unsigned long index = 0; + + renameRegEx->Replace(L"DocA", &result, index); + Assert::AreEqual(1ul, index, L"Counter should advance to 1 on first match."); + Assert::AreEqual(L"NewFile-1", result, L"First file should be renamed correctly."); + CoTaskMemFree(result); + + renameRegEx->Replace(L"DocB", &result, index); + Assert::AreEqual(2ul, index, L"Counter should advance to 2 on second match."); + Assert::AreEqual(L"NewFile-2", result, L"Second file should be renamed correctly."); + CoTaskMemFree(result); + + // The original term and the replacement are identical. + renameRegEx->Replace(L"NewFile-3", &result, index); + Assert::AreEqual(3ul, index, L"Counter must advance on a match, even if the new name is identical to the old one."); + Assert::AreEqual(L"NewFile-3", result, L"Filename should be unchanged on a coincidental match."); + CoTaskMemFree(result); + + // Test that there wasn't a "stall" in the numbering. + renameRegEx->Replace(L"DocC", &result, index); + Assert::AreEqual(4ul, index, L"Counter should continue sequentially after the coincidental match."); + Assert::AreEqual(L"NewFile-4", result, L"The subsequent file should receive the correct next number."); + CoTaskMemFree(result); +} + #ifndef TESTS_PARTIAL }; }