diff options
-rw-r--r-- | call/ScintillaCall.cxx | 8 | ||||
-rw-r--r-- | doc/ScintillaDoc.html | 25 | ||||
-rw-r--r-- | doc/ScintillaHistory.html | 4 | ||||
-rw-r--r-- | include/Scintilla.h | 1 | ||||
-rw-r--r-- | include/Scintilla.iface | 4 | ||||
-rw-r--r-- | include/ScintillaCall.h | 2 | ||||
-rw-r--r-- | include/ScintillaMessages.h | 1 | ||||
-rw-r--r-- | src/Document.cxx | 11 | ||||
-rw-r--r-- | src/Document.h | 5 | ||||
-rw-r--r-- | src/Editor.cxx | 36 | ||||
-rw-r--r-- | src/Editor.h | 9 | ||||
-rw-r--r-- | test/simpleTests.py | 39 |
12 files changed, 128 insertions, 17 deletions
diff --git a/call/ScintillaCall.cxx b/call/ScintillaCall.cxx index 816f25eb9..27ad8a738 100644 --- a/call/ScintillaCall.cxx +++ b/call/ScintillaCall.cxx @@ -130,6 +130,10 @@ Position ScintillaCall::ReplaceTargetRE(std::string_view text) { return CallString(Message::ReplaceTargetRE, text.length(), text.data()); } +Position ScintillaCall::ReplaceTargetMinimal(std::string_view text) { + return CallString(Message::ReplaceTargetMinimal, text.length(), text.data()); +} + Position ScintillaCall::SearchInTarget(std::string_view text) { return CallString(Message::SearchInTarget, text.length(), text.data()); } @@ -1419,6 +1423,10 @@ Position ScintillaCall::ReplaceTargetRE(Position length, const char *text) { return CallString(Message::ReplaceTargetRE, length, text); } +Position ScintillaCall::ReplaceTargetMinimal(Position length, const char *text) { + return CallString(Message::ReplaceTargetMinimal, length, text); +} + Position ScintillaCall::SearchInTarget(Position length, const char *text) { return CallString(Message::SearchInTarget, length, text); } diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html index cab6e397c..8fe205014 100644 --- a/doc/ScintillaDoc.html +++ b/doc/ScintillaDoc.html @@ -129,7 +129,7 @@ <h1>Scintilla Documentation</h1> - <p>Last edited 26 October 2022 NH</p> + <p>Last edited 15 November 2022 NH</p> <p style="background:#90F0C0">Scintilla 5 has moved the lexers from Scintilla into a new <a href="Lexilla.html">Lexilla</a> project.<br /> @@ -821,6 +821,7 @@ struct Sci_TextRangeFull { <a class="message" href="#SCI_SEARCHINTARGET">SCI_SEARCHINTARGET(position length, const char *text) → position</a><br /> <a class="message" href="#SCI_GETTARGETTEXT">SCI_GETTARGETTEXT(<unused>, char *text) → position</a><br /> <a class="message" href="#SCI_REPLACETARGET">SCI_REPLACETARGET(position length, const char *text) → position</a><br /> + <a class="message" href="#SCI_REPLACETARGETMINIMAL">SCI_REPLACETARGETMINIMAL(position length, const char *text) → position</a><br /> <a class="message" href="#SCI_REPLACETARGETRE">SCI_REPLACETARGETRE(position length, const char *text) → position</a><br /> <a class="message" href="#SCI_GETTAG">SCI_GETTAG(int tagNumber, char *tagValue) → int</a><br /> </code> @@ -870,9 +871,21 @@ struct Sci_TextRangeFull { <p><b id="SCI_REPLACETARGET">SCI_REPLACETARGET(position length, const char *text) → position</b><br /> If <code class="parameter">length</code> is -1, <code class="parameter">text</code> is a zero terminated string, otherwise <code class="parameter">length</code> sets the number of character to replace the target with. - After replacement, the target range refers to the replacement text. - The return value - is the length of the replacement string.<br /> + After replacement, the target range refers to the replacement text. + The return value is the length of the replacement string.<br /> + Note that the recommended way to delete text in the document is to set the target to the text to be removed, + and to perform a replace target with an empty string.</p> + + <p><b id="SCI_REPLACETARGETMINIMAL">SCI_REPLACETARGETMINIMAL(position length, const char *text) → position</b><br /> + This is similar to <a class="message" href="#SCI_REPLACETARGET"><code>SCI_REPLACETARGET</code></a> + but tries to minimize change history when the current target text shares a common prefix or suffix with the replacement. + Only the text that is actually different is marked as changed. + This might be used when automatically reformatting some text + so that the whole area formatted doesn't show change marks. + If <code class="parameter">length</code> is -1, <code class="parameter">text</code> is a zero terminated string, otherwise + <code class="parameter">length</code> sets the number of character to replace the target with. + After replacement, the target range refers to the replacement text. + The return value is the length of the replacement string.<br /> Note that the recommended way to delete text in the document is to set the target to the text to be removed, and to perform a replace target with an empty string.</p> @@ -882,8 +895,8 @@ struct Sci_TextRangeFull { characters to use. The replacement string is formed from the text string with any sequences of <code>\1</code> through <code>\9</code> replaced by tagged matches from the most recent regular expression search. <code>\0</code> is replaced with all the matched text from the most recent search. - After replacement, the target range refers to the replacement text. - The return value is the length of the replacement string.</p> + After replacement, the target range refers to the replacement text. + The return value is the length of the replacement string.</p> <p><b id="SCI_GETTAG">SCI_GETTAG(int tagNumber, char *tagValue NUL-terminated) → int</b><br /> Discover what text was matched by tagged expressions in a regular expression search. diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html index 87303cb4f..d3787329c 100644 --- a/doc/ScintillaHistory.html +++ b/doc/ScintillaHistory.html @@ -585,6 +585,10 @@ Released 12 October 2022. </li> <li> + Add SCI_REPLACETARGETMINIMAL to change text without causing unchanged prefix and suffix + to be marked as modified in change history. + </li> + <li> Draw background colour for EOL annotations with standard and boxed visuals. </li> <li> diff --git a/include/Scintilla.h b/include/Scintilla.h index 1baee5e49..43a41e986 100644 --- a/include/Scintilla.h +++ b/include/Scintilla.h @@ -557,6 +557,7 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP #define SCI_TARGETWHOLEDOCUMENT 2690 #define SCI_REPLACETARGET 2194 #define SCI_REPLACETARGETRE 2195 +#define SCI_REPLACETARGETMINIMAL 2779 #define SCI_SEARCHINTARGET 2197 #define SCI_SETSEARCHFLAGS 2198 #define SCI_GETSEARCHFLAGS 2199 diff --git a/include/Scintilla.iface b/include/Scintilla.iface index e65b5e5d7..9f02c78d2 100644 --- a/include/Scintilla.iface +++ b/include/Scintilla.iface @@ -1450,6 +1450,10 @@ fun position ReplaceTarget=2194(position length, string text) # caused by processing the \d patterns. fun position ReplaceTargetRE=2195(position length, string text) +# Replace the target text with the argument text but ignore prefix and suffix that +# are the same as current. +fun position ReplaceTargetMinimal=2779(position length, string text) + # Search for a counted string in the target and set the target to the found # range. Text is counted so it can contain NULs. # Returns start of found range or -1 for failure in which case target is not moved. diff --git a/include/ScintillaCall.h b/include/ScintillaCall.h index 9f3b7e40a..9c851d567 100644 --- a/include/ScintillaCall.h +++ b/include/ScintillaCall.h @@ -71,6 +71,7 @@ public: std::string StringOfRange(Span span); Position ReplaceTarget(std::string_view text); Position ReplaceTargetRE(std::string_view text); + Position ReplaceTargetMinimal(std::string_view text); Position SearchInTarget(std::string_view text); Span SpanSearchInTarget(std::string_view text); @@ -393,6 +394,7 @@ public: void TargetWholeDocument(); Position ReplaceTarget(Position length, const char *text); Position ReplaceTargetRE(Position length, const char *text); + Position ReplaceTargetMinimal(Position length, const char *text); Position SearchInTarget(Position length, const char *text); void SetSearchFlags(Scintilla::FindOption searchFlags); Scintilla::FindOption SearchFlags(); diff --git a/include/ScintillaMessages.h b/include/ScintillaMessages.h index 9d3034f79..12b2c2504 100644 --- a/include/ScintillaMessages.h +++ b/include/ScintillaMessages.h @@ -323,6 +323,7 @@ enum class Message { TargetWholeDocument = 2690, ReplaceTarget = 2194, ReplaceTargetRE = 2195, + ReplaceTargetMinimal = 2779, SearchInTarget = 2197, SetSearchFlags = 2198, GetSearchFlags = 2199, diff --git a/src/Document.cxx b/src/Document.cxx index 0237cef78..6a638471d 100644 --- a/src/Document.cxx +++ b/src/Document.cxx @@ -1242,6 +1242,17 @@ void Document::CheckReadOnly() { } } +void Document::TrimReplacement(std::string_view &text, Range &range) const noexcept { + while (!text.empty() && !range.Empty() && (text.front() == CharAt(range.start))) { + text.remove_prefix(1); + range.start++; + } + while (!text.empty() && !range.Empty() && (text.back() == CharAt(range.end-1))) { + text.remove_suffix(1); + range.end--; + } +} + // Document only modified by gateways DeleteChars, InsertString, Undo, Redo, and SetStyleAt. // SetStyleAt does not change the persistent state of a document diff --git a/src/Document.h b/src/Document.h index ae784180a..ac9c6670c 100644 --- a/src/Document.h +++ b/src/Document.h @@ -46,6 +46,10 @@ public: return (start != Sci::invalidPosition) && (end != Sci::invalidPosition); } + [[nodiscard]] bool Empty() const noexcept { + return start == end; + } + Sci::Position First() const noexcept { return (start <= end) ? start : end; } @@ -369,6 +373,7 @@ public: // Gateways to modifying document void ModifiedAt(Sci::Position pos) noexcept; void CheckReadOnly(); + void TrimReplacement(std::string_view &text, Range &range) const noexcept; bool DeleteChars(Sci::Position pos, Sci::Position len); Sci::Position InsertString(Sci::Position position, const char *s, Sci::Position insertLength); Sci::Position InsertString(Sci::Position position, std::string_view sv); diff --git a/src/Editor.cxx b/src/Editor.cxx index 2e1467e32..1fb6d960d 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -5696,15 +5696,27 @@ Sci::Position Editor::GetTag(char *tagValue, int tagNumber) { return length; } -Sci::Position Editor::ReplaceTarget(bool replacePatterns, const char *text, Sci::Position length) { +Sci::Position Editor::ReplaceTarget(ReplaceType replaceType, std::string_view text) { UndoGroup ug(pdoc); - if (length == -1) - length = strlen(text); - if (replacePatterns) { - text = pdoc->SubstituteByPosition(text, &length); - if (!text) { + if (replaceType == ReplaceType::patterns) { + Sci::Position length = text.length(); + const char *p = pdoc->SubstituteByPosition(text.data(), &length); + if (!p) { return 0; } + text = std::string_view(p, length); + } + + if (replaceType == ReplaceType::minimal) { + // Check for prefix and suffix and reduce text and target to match. + // This is performed with Range which doesn't support virtual space. + Range range(targetRange.start.Position(), targetRange.end.Position()); + pdoc->TrimReplacement(text, range); + // Re-apply virtual space to start if start position didn't change. + // Don't bother with end as its virtual space is not used + const SelectionPosition start(range.start == targetRange.start.Position() ? + targetRange.start : SelectionPosition(range.start)); + targetRange = SelectionSegment(start, SelectionPosition(range.end)); } // Remove the text inside the range @@ -5718,9 +5730,9 @@ Sci::Position Editor::ReplaceTarget(bool replacePatterns, const char *text, Sci: targetRange.end = targetRange.start; // Insert the new text - const Sci::Position lengthInserted = pdoc->InsertString(targetRange.start.Position(), text, length); + const Sci::Position lengthInserted = pdoc->InsertString(targetRange.start.Position(), text); targetRange.end.SetPosition(targetRange.start.Position() + lengthInserted); - return length; + return text.length(); } bool Editor::IsUnicodeMode() const noexcept { @@ -6240,11 +6252,15 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) { case Message::ReplaceTarget: PLATFORM_ASSERT(lParam); - return ReplaceTarget(false, ConstCharPtrFromSPtr(lParam), PositionFromUPtr(wParam)); + return ReplaceTarget(ReplaceType::basic, ViewFromParams(lParam, wParam)); case Message::ReplaceTargetRE: PLATFORM_ASSERT(lParam); - return ReplaceTarget(true, ConstCharPtrFromSPtr(lParam), PositionFromUPtr(wParam)); + return ReplaceTarget(ReplaceType::patterns, ViewFromParams(lParam, wParam)); + + case Message::ReplaceTargetMinimal: + PLATFORM_ASSERT(lParam); + return ReplaceTarget(ReplaceType::minimal, ViewFromParams(lParam, wParam)); case Message::SearchInTarget: PLATFORM_ASSERT(lParam); diff --git a/src/Editor.h b/src/Editor.h index 128abf234..85d95e21d 100644 --- a/src/Editor.h +++ b/src/Editor.h @@ -581,7 +581,8 @@ protected: // ScintillaBase subclass needs access to much of Editor void FoldAll(Scintilla::FoldAction action); Sci::Position GetTag(char *tagValue, int tagNumber); - Sci::Position ReplaceTarget(bool replacePatterns, const char *text, Sci::Position length=-1); + enum class ReplaceType {basic, patterns, minimal}; + Sci::Position ReplaceTarget(ReplaceType replaceType, std::string_view text); bool PositionIsHotspot(Sci::Position position) const noexcept; bool PointIsHotspot(Point pt); @@ -625,6 +626,12 @@ protected: // ScintillaBase subclass needs access to much of Editor static unsigned char *UCharPtrFromSPtr(Scintilla::sptr_t lParam) noexcept { return static_cast<unsigned char *>(PtrFromSPtr(lParam)); } + static std::string_view ViewFromParams(Scintilla::sptr_t lParam, Scintilla::uptr_t wParam) noexcept { + if (SPtrFromUPtr(wParam) == -1) { + return std::string_view(CharPtrFromSPtr(lParam)); + } + return std::string_view(CharPtrFromSPtr(lParam), wParam); + } static void *PtrFromUPtr(Scintilla::uptr_t wParam) noexcept { return reinterpret_cast<void *>(wParam); } diff --git a/test/simpleTests.py b/test/simpleTests.py index 2b5d3bc0e..31ae42535 100644 --- a/test/simpleTests.py +++ b/test/simpleTests.py @@ -634,6 +634,45 @@ class TestSimple(unittest.TestCase): self.assertEquals(self.ed.TargetStart, 4) self.assertEquals(self.ed.TargetEnd, 5) + def testReplaceTargetMinimal(self): + # 1: No common characters + self.ed.SetContents(b"abcd") + self.ed.TargetStart = 1 + self.ed.TargetEnd = 3 + self.assertEquals(self.ed.TargetStart, 1) + self.assertEquals(self.ed.TargetEnd, 3) + rep = b"321" + self.ed.ReplaceTargetMinimal(len(rep), rep) + self.assertEquals(self.ed.Contents(), b"a321d") + + # 2: New characters with common prefix and suffix + self.ed.TargetStart = 1 + self.ed.TargetEnd = 4 + rep = b"3<>1" + self.ed.ReplaceTargetMinimal(len(rep), rep) + self.assertEquals(self.ed.Contents(), b"a3<>1d") + + # 3: Remove characters with common prefix and suffix + self.ed.TargetStart = 1 + self.ed.TargetEnd = 5 + rep = b"31" + self.ed.ReplaceTargetMinimal(len(rep), rep) + self.assertEquals(self.ed.Contents(), b"a31d") + + # 4: Common prefix + self.ed.TargetStart = 1 + self.ed.TargetEnd = 3 + rep = b"3bc" + self.ed.ReplaceTargetMinimal(len(rep), rep) + self.assertEquals(self.ed.Contents(), b"a3bcd") + + # 5: Common suffix + self.ed.TargetStart = 2 + self.ed.TargetEnd = 5 + rep = b"cd" + self.ed.ReplaceTargetMinimal(len(rep), rep) + self.assertEquals(self.ed.Contents(), b"a3cd") + def testTargetWhole(self): self.ed.SetContents(b"abcd") self.ed.TargetStart = 1 |