Skip to content

Commit 9863cea

Browse files
committed
Count option for replace and rereplace
1 parent c6850ec commit 9863cea

File tree

9 files changed

+100
-20
lines changed

9 files changed

+100
-20
lines changed

PythonScript.Tests/TestRunner.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ void runReplace()
1616

1717
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
1818
std::list<NppPythonScript::ReplaceEntry* > entries;
19-
bool moreEntries = replacer.startReplace("aaabbbaaabb", 12, 0, "(b+)", "x$1x", NppPythonScript::python_re_flag_normal, entries);
19+
bool moreEntries = replacer.startReplace("aaabbbaaabb", 12, 0, 0, "(b+)", "x$1x", NppPythonScript::python_re_flag_normal, entries);
2020
ASSERT_EQ(2, entries.size());
2121
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
2222
for_each(entries.begin(), entries.end(), deleteEntry);

PythonScript.Tests/tests/TestReplacer.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ void deleteEntry(ReplaceEntry* entry)
3232
TEST_F(ReplacerTest, SimpleReplace) {
3333
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
3434
std::list<NppPythonScript::ReplaceEntry* > entries;
35-
bool moreEntries = replacer.startReplace("aaabbbaaabb", 12, 0, "(b+)", "x$1x", NppPythonScript::python_re_flag_normal, entries);
35+
bool moreEntries = replacer.startReplace("aaabbbaaabb", 12, 0, 0, "(b+)", "x$1x", NppPythonScript::python_re_flag_normal, entries);
3636
ASSERT_EQ(2, entries.size());
3737
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
3838

@@ -56,7 +56,7 @@ TEST_F(ReplacerTest, SimpleReplace) {
5656
TEST_F(ReplacerTest, ReplaceUtf8) {
5757
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
5858
std::list<NppPythonScript::ReplaceEntry* > entries;
59-
bool moreEntries = replacer.startReplace("aaa\xc3\xb4" "bbbaaa\xc3\xbc" "bb", 15, 0, "aaa([\xc3\xbc])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
59+
bool moreEntries = replacer.startReplace("aaa\xc3\xb4" "bbbaaa\xc3\xbc" "bb", 15, 0, 0, "aaa([\xc3\xbc])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
6060
ASSERT_EQ(1, entries.size());
6161
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
6262

@@ -73,7 +73,7 @@ TEST_F(ReplacerTest, ReplaceUtf8) {
7373
TEST_F(ReplacerTest, ReplaceExtendedUtf8) {
7474
NppPythonScript::Replacer<NppPythonScript::Utf8CharTraits> replacer;
7575
std::list<NppPythonScript::ReplaceEntry* > entries;
76-
bool moreEntries = replacer.startReplace("aaa\xF0\x9F\x82\xB7" "ZZZ" "bbbaaa\xF0\x9F\x82\xB8" "ZZZ", 23, 0, "aaa([\xF0\x9F\x82\xB8])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
76+
bool moreEntries = replacer.startReplace("aaa\xF0\x9F\x82\xB7" "ZZZ" "bbbaaa\xF0\x9F\x82\xB8" "ZZZ", 23, 0, 0, "aaa([\xF0\x9F\x82\xB8])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
7777
ASSERT_EQ(1, entries.size());
7878
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
7979

@@ -90,7 +90,7 @@ TEST_F(ReplacerTest, ReplaceSimpleAnsi) {
9090

9191
NppPythonScript::Replacer<NppPythonScript::AnsiCharTraits> replacer;
9292
std::list<NppPythonScript::ReplaceEntry* > entries;
93-
bool moreEntries = replacer.startReplace("aaa\xF0\x9F" "ZZZ" "aaa\x9F\xB8" "ZZZ", 16, 0, "aaa([\xF0\x9F])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
93+
bool moreEntries = replacer.startReplace("aaa\xF0\x9F" "ZZZ" "aaa\x9F\xB8" "ZZZ", 16, 0, 0, "aaa([\xF0\x9F])", "x$1x", NppPythonScript::python_re_flag_normal,entries);
9494
ASSERT_EQ(2, entries.size());
9595
std::list<NppPythonScript::ReplaceEntry*>::const_iterator it = entries.begin();
9696
ASSERT_EQ(0, (*it)->getStart());

PythonScript/project/PythonScript2010.vcxproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,9 @@ xcopy $(ProjectDir)..\python_tests\*.* "e:\notepadtest\unicode\plugins\config\py
238238
<ClInclude Include="..\python_tests\tests\ReplaceStartEndTestCase.py">
239239
<FileType>Document</FileType>
240240
</ClInclude>
241+
<ClInclude Include="..\python_tests\tests\ReplaceCountTestCase.py">
242+
<FileType>Document</FileType>
243+
</ClInclude>
241244
<None Include="..\python_tests\tests\__init__.py" />
242245
<None Include="..\python_tests\__init__.py" />
243246
<None Include="..\res\FolderClosed.ico" />

PythonScript/project/PythonScript2010.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,9 @@
287287
<ClInclude Include="..\python_tests\tests\ReplaceStartEndTestCase.py">
288288
<Filter>PythonTests\Tests</Filter>
289289
</ClInclude>
290+
<ClInclude Include="..\python_tests\tests\ReplaceCountTestCase.py">
291+
<Filter>PythonTests\Tests</Filter>
292+
</ClInclude>
290293
</ItemGroup>
291294
<ItemGroup>
292295
<ResourceCompile Include="..\res\PythonScript.rc">
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# -*- coding: utf-8 -*-
2+
import unittest
3+
import re
4+
from Npp import *
5+
6+
class ReplaceStartEndTestCase(unittest.TestCase):
7+
def setUp(self):
8+
notepad.new()
9+
notepad.runMenuCommand("Encoding", "Encode in UTF-8")
10+
editor.write(u'Abc123DEF4567 ghi8910\r\nAbc123\r\n')
11+
12+
def tearDown(self):
13+
editor.setSavePoint()
14+
notepad.close()
15+
16+
def test_replace_limit_3(self):
17+
editor.rereplace(r'([A-Z]{3})', 'TEST', re.IGNORECASE, 0, 0, 3)
18+
text = editor.getText()
19+
self.assertEqual(text, u'TEST123TEST4567 TEST8910\r\nAbc123\r\n')
20+
21+
def test_replace_limit_2(self):
22+
editor.rereplace(r'([A-Z]{3})', 'TEST', re.IGNORECASE, 0, 0, 2)
23+
text = editor.getText()
24+
self.assertEqual(text, u'TEST123TEST4567 ghi8910\r\nAbc123\r\n')
25+
26+
27+
def test_replace_literal_count_1(self):
28+
editor.replace(r'Abc', 'TEST', re.IGNORECASE, 0, 0, 1)
29+
text = editor.getText()
30+
self.assertEqual(text, u'TEST123DEF4567 ghi8910\r\nAbc123\r\n')
31+
32+
suite = unittest.TestLoader().loadTestsFromTestCase(ReplaceStartEndTestCase)

PythonScript/src/Replacer.h

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ typename std::string BoostRegexMatch<CharTraitsT>::getTextForGroup(GroupDetail*
180180
{ }
181181

182182

183-
bool startReplace(const char *text, const int textLength, const int startPosition, const char *search, matchConverter converter, void *converterState, python_re_flags flags, std::list<ReplaceEntry*>& replacements);
184-
bool startReplace(const char *text, const int textLength, const int startPosition, const char *search, const char *replace, python_re_flags flags, std::list<ReplaceEntry*>& replacements);
183+
bool startReplace(const char *text, const int textLength, int maxCount, const int startPosition, const char *search, matchConverter converter, void *converterState, python_re_flags flags, std::list<ReplaceEntry*>& replacements);
184+
bool startReplace(const char *text, const int textLength, int maxCount, const int startPosition, const char *search, const char *replace, python_re_flags flags, std::list<ReplaceEntry*>& replacements);
185185

186186
private:
187187
static ReplaceEntry* matchToReplaceEntry(const char *text, Match *match, void *state);
@@ -211,13 +211,14 @@ ReplaceEntry* NppPythonScript::Replacer<CharTraitsT>::matchToReplaceEntry(const
211211

212212
template<class CharTraitsT>
213213
bool NppPythonScript::Replacer<CharTraitsT>::startReplace(const char *text, const int textLength, const int startPosition,
214+
int maxCount,
214215
const char *search,
215216
const char *replace,
216217
python_re_flags flags,
217218
std::list<ReplaceEntry*>& replacements)
218219
{
219220
m_replaceFormat = replace;
220-
return startReplace(text, textLength, startPosition, search, matchToReplaceEntry, this, flags, replacements);
221+
return startReplace(text, textLength, startPosition, maxCount, search, matchToReplaceEntry, this, flags, replacements);
221222
}
222223

223224
template<class CharTraitsT>
@@ -263,11 +264,15 @@ boost::regex_constants::syntax_option_type Replacer<CharTraitsT>::getSyntaxFlags
263264
}
264265

265266
template<class CharTraitsT>
266-
bool NppPythonScript::Replacer<CharTraitsT>::startReplace(const char *text, const int textLength, const int startPosition, const char *search,
267+
bool NppPythonScript::Replacer<CharTraitsT>::startReplace(const char *text, const int textLength,
268+
const int startPosition,
269+
int maxCount,
270+
const char *search,
267271
matchConverter converter,
268272
void *converterState,
269273
python_re_flags flags,
270-
std::list<ReplaceEntry*> &replacements) {
274+
std::list<ReplaceEntry*> &replacements)
275+
{
271276

272277
boost::regex_constants::syntax_option_type syntax_flags = getSyntaxFlags(flags);
273278

@@ -277,12 +282,26 @@ bool NppPythonScript::Replacer<CharTraitsT>::startReplace(const char *text, cons
277282
CharTraitsT::text_iterator_type end(text, textLength, textLength);
278283
CharTraitsT::regex_iterator_type iteratorEnd;
279284
BoostRegexMatch<CharTraitsT> match(text);
280-
for(CharTraitsT::regex_iterator_type it(start, end, r, getMatchFlags(flags)); it != iteratorEnd; ++it) {
285+
286+
bool checkCountOfReplaces = false;
287+
if (maxCount > 0)
288+
{
289+
checkCountOfReplaces = true;
290+
}
291+
292+
293+
294+
for(CharTraitsT::regex_iterator_type it(start, end, r, getMatchFlags(flags)); it != iteratorEnd; ++it)
295+
{
281296
boost::match_results<CharTraitsT::text_iterator_type> boost_match_results(*it);
282297

283298
match.setMatchResults(&boost_match_results);
284299
ReplaceEntry* entry = converter(text, &match, converterState);
285300
replacements.push_back(entry);
301+
if (checkCountOfReplaces && 0 == --maxCount)
302+
{
303+
break;
304+
}
286305
}
287306

288307
return false;

PythonScript/src/ScintillaPython.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ BOOST_PYTHON_MODULE(Npp)
3535
.def("replace", &ScintillaWrapper::replacePlain, boost::python::args("search", "replace"), "Simple search and replace. Replace [search] with [replace]")
3636
.def("replace", &ScintillaWrapper::replacePlainFlags, boost::python::args("search", "replace", "flags"), "Simple search and replace. Replace 'search' with 'replace' using the given flags.\nFlags are from the re module, and only re.IGNORECASE has an effect. ")
3737
.def("replace", &ScintillaWrapper::replacePlainFlagsStart, boost::python::args("search", "replace", "flags", "startPosition"), "Simple search and replace. Replace 'search' with 'replace' using the given flags.\nFlags are from the re module, and only re.IGNORECASE has an effect. Starts from the given (binary) startPosition")
38-
.def("replace", &ScintillaWrapper::replacePlainFlagsStartEnd, boost::python::args("search", "replace", "flags", "startPosition", "endPosition"), "Simple search and replace. Replace 'search' with 'replace' using the given flags.\nFlags are from the re module, and only re.IGNORECASE has an effect. Starts from the given (binary) startPosition")
38+
.def("replace", &ScintillaWrapper::replacePlainFlagsStartEnd, boost::python::args("search", "replace", "flags", "startPosition", "endPosition"), "Simple search and replace. Replace 'search' with 'replace' using the given flags.\nFlags are from the re module, and only re.IGNORECASE has an effect. Starts from the given (binary) startPosition, and replaces until the endPosition has been reached.")
39+
.def("replace", &ScintillaWrapper::replacePlainFlagsStartEndMaxCount, boost::python::args("search", "replace", "flags", "startPosition", "endPosition", "maxCount"), "Simple search and replace. Replace 'search' with 'replace' using the given flags.\nFlags are from the re module, and only re.IGNORECASE has an effect. Starts from the given (binary) startPosition, replaces until either the endPosition has been reached, or the maxCount of replacements have been performed")
3940
.def("rereplace", &ScintillaWrapper::replaceRegex, boost::python::args("searchRegex", "replace"), "Regular expression search and replace. Replaces 'searchRegex' with 'replace'. ^ and $ by default match the starts and end of the document. Use additional flags (re.MULTILINE) to treat ^ and $ per line.\n"
4041
"The 'replace' parameter can be a python function, that recieves an object similar to a re.Match object.\n"
4142
"So you can have a function like\n"
@@ -64,6 +65,15 @@ BOOST_PYTHON_MODULE(Npp)
6465
" return int(m.group(1)) + 1\n\n"
6566
"And call rereplace('([0-9]+)', myIncrement) and it will increment all the integers.")
6667

68+
.def("rereplace", &ScintillaWrapper::replaceRegexFlagsStartEndMaxCount, boost::python::args("searchRegex", "replace", "flags", "startPosition", "endPosition", "maxCount"), "Regular expression search and replace. Replaces 'searchRegex' with 'replace'. Flags are the flags from the python re module (re.IGNORECASE, re.MULTILINE, re.DOTALL), and can be ORed together.\n"
69+
"startPosition and endPosition are the binary position to start and end the search from.\n"
70+
"maxCount is the maximum count of replacements to perform.\n"
71+
"^ and $ by default match the starts and end of the document. Use re.MULTILINE as the flags to treat ^ and $ per line.\n"
72+
"The 'replace' parameter can be a python function, that recieves an object similar to a re.Match object.\n"
73+
"So you can have a function like\n"
74+
" def myIncrement(m):\n"
75+
" return int(m.group(1)) + 1\n\n"
76+
"And call rereplace('([0-9]+)', myIncrement) and it will increment all the integers.")
6777
.def("getWord", &ScintillaWrapper::getWord, "getWord([position[, useOnlyWordChars]])\nGets the word at position. If position is not given or None, the current caret position is used.\nuseOnlyWordChars is a bool that is passed to Scintilla - see Scintilla rules on what is match. If not given or None, it is assumed to be true.")
6878
.def("getWord", &ScintillaWrapper::getWordNoFlags, "getWord([position[, useOnlyWordChars]])\nGets the word at position. If position is not given or None, the current caret position is used.\nuseOnlyWordChars is a bool that is passed to Scintilla - see Scintilla rules on what is match. If not given or None, it is assumed to be true.")
6979
.def("getWord", &ScintillaWrapper::getCurrentWord, "getWord([position[, useOnlyWordChars]])\nGets the word at position. If position is not given or None, the current caret position is used.\nuseOnlyWordChars is a bool that is passed to Scintilla - see Scintilla rules on what is match. If not given or None, it is assumed to be true.")

PythonScript/src/ScintillaWrapper.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -583,16 +583,21 @@ void ScintillaWrapper::replacePlain(boost::python::object searchStr, boost::pyth
583583

584584
void ScintillaWrapper::replacePlainFlags(boost::python::object searchStr, boost::python::object replaceStr, int flags)
585585
{
586-
replacePlainFlagsStartEnd(searchStr, replaceStr, flags, -1, -1);
586+
replacePlainFlagsStartEndMaxCount(searchStr, replaceStr, flags, -1, -1, 0);
587587
}
588588

589589

590590
void ScintillaWrapper::replacePlainFlagsStart(boost::python::object searchStr, boost::python::object replaceStr, int flags, int startPosition)
591591
{
592-
replacePlainFlagsStartEnd(searchStr, replaceStr, flags, startPosition, -1);
592+
replacePlainFlagsStartEndMaxCount(searchStr, replaceStr, flags, startPosition, -1, 0);
593593
}
594594

595595
void ScintillaWrapper::replacePlainFlagsStartEnd(boost::python::object searchStr, boost::python::object replaceStr, int flags, int startPosition, int endPosition)
596+
{
597+
replacePlainFlagsStartEndMaxCount(searchStr, replaceStr, flags, startPosition, endPosition, 0);
598+
}
599+
600+
void ScintillaWrapper::replacePlainFlagsStartEndMaxCount(boost::python::object searchStr, boost::python::object replaceStr, int flags, int startPosition, int endPosition, int maxCount)
596601
{
597602
NppPythonScript::python_re_flags resultFlags = NppPythonScript::python_re_flag_literal;
598603

@@ -601,7 +606,7 @@ void ScintillaWrapper::replacePlainFlagsStartEnd(boost::python::object searchStr
601606

602607

603608
replaceImpl(searchStr, replaceStr,
604-
0, // count
609+
maxCount,
605610
resultFlags,
606611
startPosition,
607612
endPosition
@@ -633,8 +638,14 @@ void ScintillaWrapper::replaceRegexFlagsStartEnd(boost::python::object searchStr
633638
}
634639

635640

641+
void ScintillaWrapper::replaceRegexFlagsStartEndMaxCount(boost::python::object searchStr, boost::python::object replaceStr, int flags, int start, int end, int count)
642+
{
643+
replaceImpl(searchStr, replaceStr, count, (NppPythonScript::python_re_flags)flags, start, end);
644+
}
645+
646+
636647
void ScintillaWrapper::replaceImpl(boost::python::object searchStr, boost::python::object replaceStr,
637-
int /* count */,
648+
int maxCount,
638649
NppPythonScript::python_re_flags flags,
639650
int startPosition,
640651
int endPosition)
@@ -674,11 +685,11 @@ void ScintillaWrapper::replaceImpl(boost::python::object searchStr, boost::pytho
674685
if (isPythonReplaceFunction)
675686
{
676687
m_pythonReplaceFunction = replaceStr;
677-
replacer.startReplace(text, length, startPosition, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), flags, replacements);
688+
replacer.startReplace(text, length, startPosition, maxCount, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), flags, replacements);
678689
}
679690
else
680691
{
681-
replacer.startReplace(text, length, startPosition, searchChars.c_str(), replaceChars.c_str(), flags, replacements);
692+
replacer.startReplace(text, length, startPosition, maxCount, searchChars.c_str(), replaceChars.c_str(), flags, replacements);
682693
}
683694
}
684695
else
@@ -688,11 +699,11 @@ void ScintillaWrapper::replaceImpl(boost::python::object searchStr, boost::pytho
688699
if (isPythonReplaceFunction)
689700
{
690701
m_pythonReplaceFunction = replaceStr;
691-
replacer.startReplace(text, length, startPosition, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), flags, replacements);
702+
replacer.startReplace(text, length, startPosition, maxCount, searchChars.c_str(), &ScintillaWrapper::convertWithPython, reinterpret_cast<void*>(this), flags, replacements);
692703
}
693704
else
694705
{
695-
replacer.startReplace(text, length, startPosition, searchChars.c_str(), replaceChars.c_str(), flags, replacements);
706+
replacer.startReplace(text, length, startPosition, maxCount, searchChars.c_str(), replaceChars.c_str(), flags, replacements);
696707
}
697708
}
698709

PythonScript/src/ScintillaWrapper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ class ScintillaWrapper : public NppPythonScript::PyProducerConsumer<CallbackExec
7676
void replacePlainFlags(boost::python::object searchStr, boost::python::object replaceStr, int flags);
7777
void replacePlainFlagsStart(boost::python::object searchStr, boost::python::object replaceStr, int flags, int startPosition);
7878
void replacePlainFlagsStartEnd(boost::python::object searchStr, boost::python::object replaceStr, int flags, int startPosition, int endPosition);
79+
void replacePlainFlagsStartEndMaxCount(boost::python::object searchStr, boost::python::object replaceStr, int flags, int startPosition, int endPosition, int maxCount);
7980
void replaceRegex(boost::python::object searchStr, boost::python::object replaceStr);
8081
void replaceRegexFlags(boost::python::object searchStr, boost::python::object replaceStr, int flags);
8182
void replaceRegexFlagsStart(boost::python::object searchStr, boost::python::object replaceStr, int flags, int start);
8283
void replaceRegexFlagsStartEnd(boost::python::object searchStr, boost::python::object replaceStr, int flags, int start, int end);
84+
void replaceRegexFlagsStartEndMaxCount(boost::python::object searchStr, boost::python::object replaceStr, int flags, int start, int end, int maxCount);
8385

8486
void replaceImpl(boost::python::object searchStr, boost::python::object replaceStr, int count, NppPythonScript::python_re_flags flags, int startPosition, int endPosition);
8587

0 commit comments

Comments
 (0)