From cb8cd73d839a96f98bb1ce887c694271f9c24788 Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 22 Nov 2022 09:24:07 +1100 Subject: Add SCI_REPLACETARGETMINIMAL to change text without causing unchanged prefix and suffix to be marked as modified in change history. --- call/ScintillaCall.cxx | 8 ++++++++ doc/ScintillaDoc.html | 25 +++++++++++++++++++------ doc/ScintillaHistory.html | 4 ++++ include/Scintilla.h | 1 + include/Scintilla.iface | 4 ++++ include/ScintillaCall.h | 2 ++ include/ScintillaMessages.h | 1 + src/Document.cxx | 11 +++++++++++ src/Document.h | 5 +++++ src/Editor.cxx | 36 ++++++++++++++++++++++++++---------- src/Editor.h | 9 ++++++++- 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 @@

Scintilla Documentation

-

Last edited 26 October 2022 NH

+

Last edited 15 November 2022 NH

Scintilla 5 has moved the lexers from Scintilla into a new Lexilla project.
@@ -821,6 +821,7 @@ struct Sci_TextRangeFull { SCI_SEARCHINTARGET(position length, const char *text) → position
SCI_GETTARGETTEXT(<unused>, char *text) → position
SCI_REPLACETARGET(position length, const char *text) → position
+ SCI_REPLACETARGETMINIMAL(position length, const char *text) → position
SCI_REPLACETARGETRE(position length, const char *text) → position
SCI_GETTAG(int tagNumber, char *tagValue) → int
@@ -870,9 +871,21 @@ struct Sci_TextRangeFull {

SCI_REPLACETARGET(position length, const char *text) → position
If length is -1, text is a zero terminated string, otherwise length 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.
+ After replacement, the target range refers to the replacement text. + The return value is the length of the replacement string.
+ 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.

+ +

SCI_REPLACETARGETMINIMAL(position length, const char *text) → position
+ This is similar to SCI_REPLACETARGET + 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 length is -1, text is a zero terminated string, otherwise + length 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.
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.

@@ -882,8 +895,8 @@ struct Sci_TextRangeFull { characters to use. The replacement string is formed from the text string with any sequences of \1 through \9 replaced by tagged matches from the most recent regular expression search. \0 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.

+ After replacement, the target range refers to the replacement text. + The return value is the length of the replacement string.

SCI_GETTAG(int tagNumber, char *tagValue NUL-terminated) → int
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.

  • + Add SCI_REPLACETARGETMINIMAL to change text without causing unchanged prefix and suffix + to be marked as modified in change history. +
  • +
  • Draw background colour for EOL annotations with standard and boxed visuals.
  • 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(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(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 -- cgit v1.2.3