aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2022-07-31 15:51:53 +1000
committerNeil <nyamatongwe@gmail.com>2022-07-31 15:51:53 +1000
commit926cb6f7d228b347db16a45e1f2632da475da1f0 (patch)
tree6263662cb8c023502d61b61eed34baa93ed35843
parent535e20189d5a2dd9b43a6ea0a74749a50678d631 (diff)
downloadscintilla-mirror-926cb6f7d228b347db16a45e1f2632da475da1f0.tar.gz
Added change history which can display document changes (modified, saved, ...)
in the margin or in the text.
-rw-r--r--call/ScintillaCall.cxx8
-rw-r--r--cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj8
-rw-r--r--doc/ScintillaDoc.html243
-rw-r--r--doc/ScintillaHistory.html6
-rw-r--r--gtk/deps.mak13
-rw-r--r--gtk/makefile1
-rw-r--r--include/Scintilla.h20
-rw-r--r--include/Scintilla.iface28
-rw-r--r--include/ScintillaCall.h2
-rw-r--r--include/ScintillaMessages.h2
-rw-r--r--include/ScintillaTypes.h21
-rw-r--r--qt/ScintillaEdit/ScintillaEdit.pro1
-rw-r--r--qt/ScintillaEditBase/ScintillaEditBase.pro2
-rw-r--r--scripts/HeaderOrder.txt1
-rw-r--r--src/CellBuffer.cxx94
-rw-r--r--src/CellBuffer.h14
-rw-r--r--src/ChangeHistory.cxx422
-rw-r--r--src/ChangeHistory.h112
-rw-r--r--src/Document.cxx28
-rw-r--r--src/Document.h8
-rw-r--r--src/EditModel.cxx2
-rw-r--r--src/EditModel.h2
-rw-r--r--src/EditView.cxx39
-rw-r--r--src/Editor.cxx14
-rw-r--r--src/SparseVector.h16
-rw-r--r--src/ViewStyle.cxx43
-rw-r--r--test/unit/Sci.natvis17
-rw-r--r--test/unit/UnitTester.vcxproj13
-rw-r--r--test/unit/makefile1
-rw-r--r--test/unit/test.mak1
-rw-r--r--test/unit/testCellBuffer.cxx593
-rw-r--r--win32/Scintilla.vcxproj2
-rw-r--r--win32/deps.mak13
-rw-r--r--win32/makefile1
-rw-r--r--win32/nmdeps.mak13
-rw-r--r--win32/scintilla.mak1
36 files changed, 1734 insertions, 71 deletions
diff --git a/call/ScintillaCall.cxx b/call/ScintillaCall.cxx
index e142af79d..1f496c53d 100644
--- a/call/ScintillaCall.cxx
+++ b/call/ScintillaCall.cxx
@@ -1151,6 +1151,14 @@ Position ScintillaCall::FormatRangeFull(bool draw, void *fr) {
return CallPointer(Message::FormatRangeFull, draw, fr);
}
+void ScintillaCall::SetChangeHistory(Scintilla::ChangeHistoryOption changeHistory) {
+ Call(Message::SetChangeHistory, static_cast<uintptr_t>(changeHistory));
+}
+
+ChangeHistoryOption ScintillaCall::ChangeHistory() {
+ return static_cast<Scintilla::ChangeHistoryOption>(Call(Message::GetChangeHistory));
+}
+
Line ScintillaCall::FirstVisibleLine() {
return Call(Message::GetFirstVisibleLine);
}
diff --git a/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj b/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj
index 5a29a5c58..ef9e5f566 100644
--- a/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj
+++ b/cocoa/Scintilla/Scintilla.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 2807B4EA28964CA40063A31A /* ChangeHistory.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 2807B4E828964CA40063A31A /* ChangeHistory.cxx */; };
+ 2807B4EB28964CA40063A31A /* ChangeHistory.h in Headers */ = {isa = PBXBuildFile; fileRef = 2807B4E928964CA40063A31A /* ChangeHistory.h */; };
282936DF24E2D55D00C84BA2 /* QuartzTextLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 282936D324E2D55D00C84BA2 /* QuartzTextLayout.h */; };
282936E024E2D55D00C84BA2 /* InfoBar.mm in Sources */ = {isa = PBXBuildFile; fileRef = 282936D424E2D55D00C84BA2 /* InfoBar.mm */; };
282936E124E2D55D00C84BA2 /* QuartzTextStyleAttribute.h in Headers */ = {isa = PBXBuildFile; fileRef = 282936D524E2D55D00C84BA2 /* QuartzTextStyleAttribute.h */; };
@@ -103,6 +105,8 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 2807B4E828964CA40063A31A /* ChangeHistory.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ChangeHistory.cxx; path = ../../src/ChangeHistory.cxx; sourceTree = "<group>"; };
+ 2807B4E928964CA40063A31A /* ChangeHistory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ChangeHistory.h; path = ../../src/ChangeHistory.h; sourceTree = "<group>"; };
282936D324E2D55D00C84BA2 /* QuartzTextLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QuartzTextLayout.h; path = ../QuartzTextLayout.h; sourceTree = "<group>"; };
282936D424E2D55D00C84BA2 /* InfoBar.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = InfoBar.mm; path = ../InfoBar.mm; sourceTree = "<group>"; };
282936D524E2D55D00C84BA2 /* QuartzTextStyleAttribute.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = QuartzTextStyleAttribute.h; path = ../QuartzTextStyleAttribute.h; sourceTree = "<group>"; };
@@ -246,6 +250,8 @@
2829372524E2D58700C84BA2 /* CaseFolder.h */,
2829372624E2D58700C84BA2 /* CellBuffer.cxx */,
2829371924E2D58600C84BA2 /* CellBuffer.h */,
+ 2807B4E828964CA40063A31A /* ChangeHistory.cxx */,
+ 2807B4E928964CA40063A31A /* ChangeHistory.h */,
28EA9CAA255894B4007710C4 /* CharacterCategoryMap.cxx */,
28EA9CAC255894B4007710C4 /* CharacterCategoryMap.h */,
28EA9CAB255894B4007710C4 /* CharacterType.cxx */,
@@ -404,6 +410,7 @@
282936E224E2D55D00C84BA2 /* ScintillaCocoa.h in Headers */,
2829373524E2D58800C84BA2 /* Style.h in Headers */,
282936E424E2D55D00C84BA2 /* PlatCocoa.h in Headers */,
+ 2807B4EB28964CA40063A31A /* ChangeHistory.h in Headers */,
2829376C24E2D58800C84BA2 /* Selection.h in Headers */,
2829376124E2D58800C84BA2 /* ScintillaBase.h in Headers */,
2829373824E2D58800C84BA2 /* RESearch.h in Headers */,
@@ -504,6 +511,7 @@
2829375524E2D58800C84BA2 /* EditModel.cxx in Sources */,
2829375124E2D58800C84BA2 /* ContractionState.cxx in Sources */,
2829374924E2D58800C84BA2 /* CallTip.cxx in Sources */,
+ 2807B4EA28964CA40063A31A /* ChangeHistory.cxx in Sources */,
2829375824E2D58800C84BA2 /* CharClassify.cxx in Sources */,
2829373324E2D58800C84BA2 /* LineMarker.cxx in Sources */,
2829374E24E2D58800C84BA2 /* KeyMap.cxx in Sources */,
diff --git a/doc/ScintillaDoc.html b/doc/ScintillaDoc.html
index 503984654..3149b06a0 100644
--- a/doc/ScintillaDoc.html
+++ b/doc/ScintillaDoc.html
@@ -129,7 +129,7 @@
<h1>Scintilla Documentation</h1>
- <p>Last edited 18 July 2022 NH</p>
+ <p>Last edited 31 July 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 />
@@ -345,149 +345,139 @@
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#ChangeHistory">Change history</a></td>
+
<td>&cir; <a class="toc" href="#SelectionAndInformation">Selection and information</a></td>
<td>&cir; <a class="toc" href="#ByCharacterOrCodeUnit">By character or UTF-16 code unit</a></td>
- <td>&cir; <a class="toc" href="#MultipleSelectionAndVirtualSpace">Multiple Selection and Virtual Space</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#MultipleSelectionAndVirtualSpace">Multiple Selection and Virtual Space</a></td>
+
<td>&cir; <a class="toc" href="#ScrollingAndAutomaticScrolling">Scrolling and automatic scrolling</a></td>
<td>&cir; <a class="toc" href="#WhiteSpace">White space</a></td>
-
- <td>&cir; <a class="toc" href="#Cursor">Cursor</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Cursor">Cursor</a></td>
+
<td>&cir; <a class="toc" href="#MouseCapture">Mouse capture</a></td>
<td>&cir; <a class="toc" href="#LineEndings">Line endings</a></td>
- <td>&cir; <a class="toc" href="#Words">Words</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Words">Words</a></td>
+
<td>&cir; <a class="toc" href="#Styling">Styling</a></td>
<td>&cir; <a class="toc" href="#StyleDefinition">Style definition</a></td>
-
- <td>&cir; <a class="toc" href="#ElementColours">Element colours</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#ElementColours">Element colours</a></td>
+
<td>&cir; <a class="toc" href="#CaretAndSelectionStyles">Selection, caret, and hotspot styles</a></td>
<td>&cir; <a class="toc" href="#CharacterRepresentations">Character representations</a></td>
-
- <td>&cir; <a class="toc" href="#Margins">Margins</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Margins">Margins</a></td>
+
<td>&cir; <a class="toc" href="#Annotations">Annotations</a></td>
<td>&cir; <a class="toc" href="#OtherSettings">Other settings</a></td>
-
- <td>&cir; <a class="toc" href="#BraceHighlighting">Brace highlighting</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#BraceHighlighting">Brace highlighting</a></td>
+
<td>&cir; <a class="toc" href="#TabsAndIndentationGuides">Tabs and Indentation
Guides</a></td>
<td>&cir; <a class="toc" href="#Markers">Markers</a></td>
-
- <td>&cir; <a class="toc" href="#Indicators">Indicators</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Indicators">Indicators</a></td>
+
<td>&cir; <a class="toc" href="#Autocompletion">Autocompletion</a></td>
<td>&cir; <a class="toc" href="#UserLists">User lists</a></td>
-
- <td>&cir; <a class="toc" href="#CallTips">Call tips</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#CallTips">Call tips</a></td>
+
<td>&cir; <a class="toc" href="#KeyboardCommands">Keyboard commands</a></td>
<td>&cir; <a class="toc" href="#KeyBindings">Key bindings</a></td>
-
- <td>&cir; <a class="toc" href="#PopupEditMenu">Popup edit menu</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#PopupEditMenu">Popup edit menu</a></td>
+
<td>&cir; <a class="toc" href="#MacroRecording">Macro recording</a></td>
<td>&cir; <a class="toc" href="#Printing">Printing</a></td>
-
- <td>&cir; <a class="toc" href="#DirectAccess">Direct access</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#DirectAccess">Direct access</a></td>
+
<td>&cir; <a class="toc" href="#MultipleViews">Multiple views</a></td>
<td>&cir; <a class="toc" href="#BackgroundLoadSave">Background loading and saving</a></td>
-
- <td>&cir; <a class="toc" href="#Folding">Folding</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#Folding">Folding</a></td>
+
<td>&cir; <a class="toc" href="#LineWrapping">Line wrapping</a></td>
<td>&cir; <a class="toc" href="#Zooming">Zooming</a></td>
-
- <td>&cir; <a class="toc" href="#LongLines">Long lines</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#LongLines">Long lines</a></td>
+
<td>&cir; <a class="toc" href="#Accessibility">Accessibility</a></td>
<td>&cir; <a class="toc" href="#Lexer">Lexer</a></td>
-
- <td>&cir; <a class="toc" href="#LexerObjects">Lexer objects</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#LexerObjects">Lexer objects</a></td>
+
<td>&cir; <a class="toc" href="#Notifications">Notifications</a></td>
<td>&cir; <a class="toc" href="#Images">Images</a></td>
-
- <td>&cir; <a class="toc" href="#GTK">GTK</a></td>
-
</tr>
<tr>
+ <td>&cir; <a class="toc" href="#GTK">GTK</a></td>
+
<td>&cir; <a class="toc" href="#ProvisionalMessages"><span class="provisional">Provisional messages</span></a></td>
<td>&cir; <a class="toc" href="#DeprecatedMessages">Deprecated messages</a></td>
+ </tr>
+ <tr>
<td>&cir; <a class="toc" href="#EditMessagesNeverSupportedByScintilla">Edit messages never
supported by Scintilla</a></td>
- </tr>
-
- <tr>
<td>&cir; <a class="toc" href="#RemovedFeatures">Removed features</a></td>
<td>&cir; <a class="toc" href="#BuildingScintilla">Building Scintilla</a></td>
+ </tr>
+ <tr>
<td>&cir; <a class="toc" href="#EndOfLineAnnotations">End of Line Annotations</a></td>
-
</tr>
</tbody>
</table>
@@ -1343,6 +1333,157 @@ struct Sci_TextToFindFull {
Coalescing treats coalescible container actions as transparent so will still only group together insertions that
look like typing or deletions that look like multiple uses of the Backspace or Delete keys.
</p>
+
+ <h2 id="ChangeHistory">Change history</h2>
+
+ <p>Scintilla can display document changes (modified, saved, ...) in the margin or in the text.</p>
+
+ <p>This feature uses a moderate amount of memory proportional to the amount of modifications made.
+ On huge documents, this could be significant so could be disabled when it would cause excessive memory use.</p>
+
+ <code><a class="message" href="#SCI_SETCHANGEHISTORY">SCI_SETCHANGEHISTORY(int changeHistory)</a><br />
+ <a class="message" href="#SCI_GETCHANGEHISTORY">SCI_GETCHANGEHISTORY &rarr; int</a><br />
+ </code>
+
+ <p><b id="SCI_SETCHANGEHISTORY">SCI_SETCHANGEHISTORY(int changeHistory)</b><br />
+ <b id="SCI_GETCHANGEHISTORY">SCI_GETCHANGEHISTORY &rarr; int</b><br />
+ <code>SCI_SETCHANGEHISTORY</code> turns this feature on and off and determines whether changes are visible in
+ the margin or text or both.
+ The <code class="parameter">changeHistory</code> argument can be a combination of:</p>
+
+ <table class="standard" summary="Change history state">
+ <tbody valign="top">
+ <tr>
+ <th align="left"><code>SC_CHANGE_HISTORY_DISABLED</code></th>
+
+ <td>0</td>
+
+ <td>The default: change history turned off.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>SC_CHANGE_HISTORY_ENABLED</code></th>
+
+ <td>1</td>
+
+ <td>Track changes to the document.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>SC_CHANGE_HISTORY_MARKERS</code></th>
+
+ <td>2</td>
+
+ <td>Display changes in the margin using the <code>SC_MARKNUM_HISTORY</code> markers.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>SC_CHANGE_HISTORY_INDICATORS</code></th>
+
+ <td>4</td>
+
+ <td>Display changes in the margin using the <code>INDICATOR_HISTORY</code> indicators.</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <p>There are default visuals assigned to each history marker and indicator but these may be overridden by the application.</p>
+
+ <p>Markers:</p>
+
+ <table class="standard" summary="Change history markers">
+ <tbody valign="top">
+ <tr>
+ <th align="left"><code>SC_MARKNUM_HISTORY_REVERTED_TO_ORIGIN</code></th>
+
+ <td>21</td>
+
+ <td>A change was made to this line and saved but then reverted to its original state.
+ This line is different to its state on disk.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>SC_MARKNUM_HISTORY_SAVED</code></th>
+
+ <td>22</td>
+
+ <td>This line was modified and saved. This line is the same as its state on disk.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>SC_MARKNUM_HISTORY_MODIFIED</code></th>
+
+ <td>23</td>
+
+ <td>This line was modified but not yet saved. This line is different to its state on disk.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>SC_MARKNUM_HISTORY_REVERTED_TO_MODIFIED</code></th>
+
+ <td>24</td>
+
+ <td>A change was made to this line and saved but then reverted but not to its original state.
+ This line is different to its state on disk.</td>
+ </tr>
+ </tbody>
+ </table>
+
+ <p>Indicators:</p>
+
+ <table class="standard" summary="Change history indicators">
+ <tbody valign="top">
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_REVERTED_TO_ORIGIN_INSERTION</code></th>
+ <td>36</td>
+ <td>Text was deleted and saved but then reverted to its original state.
+ This text is not present on disk.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_REVERTED_TO_ORIGIN_DELETION</code></th>
+ <td>37</td>
+ <td>Text was inserted and saved but then reverted to its original state.
+ This range is different to its state on disk.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_SAVED_INSERTION</code></th>
+ <td>38</td>
+ <td>Text was inserted and saved. This text is the same as on disk.</td>
+ </tr>
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_SAVED_DELETION</code></th>
+ <td>39</td>
+ <td>Text was deleted and saved. This range is the same as on disk.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_MODIFIED_INSERTION</code></th>
+ <td>40</td>
+ <td>Text was inserted but not yet saved. This text is not present on disk.</td>
+ </tr>
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_MODIFIED_DELETION</code></th>
+ <td>41</td>
+ <td>Text was deleted but not yet saved. This range is different to its state on disk.</td>
+ </tr>
+
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_REVERTED_TO_MODIFIED_INSERTION</code></th>
+ <td>42</td>
+ <td>Text was deleted and saved but then reverted but not to its original state.
+ This text is not present on disk.</td>
+ </tr>
+ <tr>
+ <th align="left"><code>INDICATOR_HISTORY_REVERTED_TO_MODIFIED_DELETION</code></th>
+ <td>43</td>
+ <td>Text was inserted and saved but then reverted but not to its original state.
+ This range is different to its state on disk.</td>
+ </tr>
+ </tbody>
+ </table>
+
<h2 id="SelectionAndInformation">Selection and information</h2>
<p>Scintilla maintains a selection that stretches between two points, the anchor and the
@@ -4990,7 +5131,10 @@ struct Sci_TextToFindFull {
Scintilla in folding margins, and have symbolic names of the form <code>SC_MARKNUM_</code>*,
for example <code>SC_MARKNUM_FOLDEROPEN</code>.</p>
- <p>Marker numbers 0 to 24 have no pre-defined function; you can use them to mark syntax errors
+ <p>Marker numbers 21 to 24 are used for <a class="jump" href="#ChangeHistory">Change history</a> if that is enabled but are
+ otherwise free for application use.</p>
+
+ <p>Marker numbers 0 to 20 have no pre-defined function; you can use them to mark syntax errors
or the current point of execution, break points, or whatever you need marking. If you do not
need folding, you can use all 32 for any purpose you wish.</p>
@@ -5376,11 +5520,14 @@ struct Sci_TextToFindFull {
They may also be invisible when used to track pieces of content for the application as <code>INDIC_HIDDEN</code>.</p>
<p>The <code>SCI_INDIC*</code> messages allow you to get and set the visual appearance of the
- indicators. They all use an <code class="parameter">indicator</code> argument in the range 0 to <code>INDICATOR_MAX</code>(35)
+ indicators. They all use an <code class="parameter">indicator</code> argument in the range 0 to <code>INDICATOR_MAX</code>(43)
to set the indicator to style. To prevent interference the set of indicators is divided up into a range for use
by lexers (0..7) a range for use by containers
(8=<code>INDICATOR_CONTAINER</code> .. 31=<code>INDICATOR_IME-1</code>)
- and a range for IME indicators (32=<code>INDICATOR_IME</code> .. 35=<code>INDICATOR_IME_MAX</code>).</p>
+ a range for IME indicators (32=<code>INDICATOR_IME</code> .. 35=<code>INDICATOR_IME_MAX</code>)
+ and a range for <a class="jump" href="#ChangeHistory">Change history</a>
+ (36=<code>INDICATOR_HISTORY_REVERTED_TO_ORIGIN_INSERTION</code> ..
+ 43=<code>INDICATOR_HISTORY_REVERTED_TO_MODIFIED_DELETION</code>).</p>
<p>The <code>INDICATOR_*</code> values used for dividing up indicators
were previously <code>INDIC_CONTAINER</code>, <code>INDIC_IME</code>,
diff --git a/doc/ScintillaHistory.html b/doc/ScintillaHistory.html
index 5d30b807a..11c19f1e3 100644
--- a/doc/ScintillaHistory.html
+++ b/doc/ScintillaHistory.html
@@ -575,13 +575,17 @@
</table>
<h2>Releases</h2>
<h3>
- <a href="https://www.scintilla.org/scintilla525.zip">Release 5.2.5</a>
+ <a href="https://www.scintilla.org/scintilla530.zip">Release 5.3.0</a>
</h3>
<ul>
<li>
Released 10 July 2022.
</li>
<li>
+ Added change history which can display document changes (modified, saved, ...)
+ in the margin or in the text.
+ </li>
+ <li>
Line state optimized to avoid excess allocations by always allocating for every line.
This makes SCI_GETMAXLINESTATE less useful although it can still distinguish cases
where line state was never set for any lines.
diff --git a/gtk/deps.mak b/gtk/deps.mak
index d22c22fe3..83b9482d8 100644
--- a/gtk/deps.mak
+++ b/gtk/deps.mak
@@ -138,8 +138,21 @@ CellBuffer.o: \
../src/Position.h \
../src/SplitVector.h \
../src/Partitioning.h \
+ ../src/RunStyles.h \
+ ../src/SparseVector.h \
+ ../src/ChangeHistory.h \
../src/CellBuffer.h \
../src/UniConversion.h
+ChangeHistory.o: \
+ ../src/ChangeHistory.cxx \
+ ../include/ScintillaTypes.h \
+ ../src/Debugging.h \
+ ../src/Position.h \
+ ../src/SplitVector.h \
+ ../src/Partitioning.h \
+ ../src/RunStyles.h \
+ ../src/SparseVector.h \
+ ../src/ChangeHistory.h
CharacterCategoryMap.o: \
../src/CharacterCategoryMap.cxx \
../src/CharacterCategoryMap.h
diff --git a/gtk/makefile b/gtk/makefile
index 03a2b074e..a641f18e4 100644
--- a/gtk/makefile
+++ b/gtk/makefile
@@ -128,6 +128,7 @@ SRC_OBJS = \
CaseConvert.o \
CaseFolder.o \
CellBuffer.o \
+ ChangeHistory.o \
CharacterCategoryMap.o \
CharacterType.o \
CharClassify.o \
diff --git a/include/Scintilla.h b/include/Scintilla.h
index c65cb87a3..0b1121cb9 100644
--- a/include/Scintilla.h
+++ b/include/Scintilla.h
@@ -153,6 +153,10 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP
#define SC_MARK_BOOKMARK 31
#define SC_MARK_VERTICALBOOKMARK 32
#define SC_MARK_CHARACTER 10000
+#define SC_MARKNUM_HISTORY_REVERTED_TO_ORIGIN 21
+#define SC_MARKNUM_HISTORY_SAVED 22
+#define SC_MARKNUM_HISTORY_MODIFIED 23
+#define SC_MARKNUM_HISTORY_REVERTED_TO_MODIFIED 24
#define SC_MARKNUM_FOLDEREND 25
#define SC_MARKNUM_FOLDEROPENMID 26
#define SC_MARKNUM_FOLDERMIDTAIL 27
@@ -360,7 +364,15 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP
#define INDICATOR_CONTAINER 8
#define INDICATOR_IME 32
#define INDICATOR_IME_MAX 35
-#define INDICATOR_MAX 35
+#define INDICATOR_HISTORY_REVERTED_TO_ORIGIN_INSERTION 36
+#define INDICATOR_HISTORY_REVERTED_TO_ORIGIN_DELETION 37
+#define INDICATOR_HISTORY_SAVED_INSERTION 38
+#define INDICATOR_HISTORY_SAVED_DELETION 39
+#define INDICATOR_HISTORY_MODIFIED_INSERTION 40
+#define INDICATOR_HISTORY_MODIFIED_DELETION 41
+#define INDICATOR_HISTORY_REVERTED_TO_MODIFIED_INSERTION 42
+#define INDICATOR_HISTORY_REVERTED_TO_MODIFIED_DELETION 43
+#define INDICATOR_MAX 43
#define SCI_INDICSETSTYLE 2080
#define SCI_INDICGETSTYLE 2081
#define SCI_INDICSETFORE 2082
@@ -477,6 +489,12 @@ typedef sptr_t (*SciFnDirectStatus)(sptr_t ptr, unsigned int iMessage, uptr_t wP
#define SCI_FINDTEXTFULL 2196
#define SCI_FORMATRANGE 2151
#define SCI_FORMATRANGEFULL 2777
+#define SC_CHANGE_HISTORY_DISABLED 0
+#define SC_CHANGE_HISTORY_ENABLED 1
+#define SC_CHANGE_HISTORY_MARKERS 2
+#define SC_CHANGE_HISTORY_INDICATORS 4
+#define SCI_SETCHANGEHISTORY 2780
+#define SCI_GETCHANGEHISTORY 2781
#define SCI_GETFIRSTVISIBLELINE 2152
#define SCI_GETLINE 2153
#define SCI_GETLINECOUNT 2154
diff --git a/include/Scintilla.iface b/include/Scintilla.iface
index bfa7f5e93..09a83142b 100644
--- a/include/Scintilla.iface
+++ b/include/Scintilla.iface
@@ -385,7 +385,11 @@ ali SC_MARK_RGBAIMAGE=RGBA_IMAGE
ali SC_MARK_VERTICALBOOKMARK=VERTICAL_BOOKMARK
enu MarkerOutline=SC_MARKNUM_
-# Markers used for outlining column.
+# Markers used for outlining and change history columns.
+val SC_MARKNUM_HISTORY_REVERTED_TO_ORIGIN=21
+val SC_MARKNUM_HISTORY_SAVED=22
+val SC_MARKNUM_HISTORY_MODIFIED=23
+val SC_MARKNUM_HISTORY_REVERTED_TO_MODIFIED=24
val SC_MARKNUM_FOLDEREND=25
val SC_MARKNUM_FOLDEROPENMID=26
val SC_MARKNUM_FOLDERMIDTAIL=27
@@ -855,7 +859,15 @@ enu IndicatorNumbers=INDICATOR_
val INDICATOR_CONTAINER=8
val INDICATOR_IME=32
val INDICATOR_IME_MAX=35
-val INDICATOR_MAX=35
+val INDICATOR_HISTORY_REVERTED_TO_ORIGIN_INSERTION=36
+val INDICATOR_HISTORY_REVERTED_TO_ORIGIN_DELETION=37
+val INDICATOR_HISTORY_SAVED_INSERTION=38
+val INDICATOR_HISTORY_SAVED_DELETION=39
+val INDICATOR_HISTORY_MODIFIED_INSERTION=40
+val INDICATOR_HISTORY_MODIFIED_DELETION=41
+val INDICATOR_HISTORY_REVERTED_TO_MODIFIED_INSERTION=42
+val INDICATOR_HISTORY_REVERTED_TO_MODIFIED_DELETION=43
+val INDICATOR_MAX=43
ali INDIC_TT=T_T
ali INDIC_ROUNDBOX=ROUND_BOX
@@ -1224,6 +1236,18 @@ fun position FormatRange=2151(bool draw, formatrange fr)
# Draw the document into a display context such as a printer.
fun position FormatRangeFull=2777(bool draw, formatrangefull fr)
+enu ChangeHistoryOption=SC_CHANGE_HISTORY_
+val SC_CHANGE_HISTORY_DISABLED=0
+val SC_CHANGE_HISTORY_ENABLED=1
+val SC_CHANGE_HISTORY_MARKERS=2
+val SC_CHANGE_HISTORY_INDICATORS=4
+
+# Enable or disable change history.
+set void SetChangeHistory=2780(ChangeHistoryOption changeHistory,)
+
+# Report change history status.
+get ChangeHistoryOption GetChangeHistory=2781(,)
+
# Retrieve the display line at the top of the display.
get line GetFirstVisibleLine=2152(,)
diff --git a/include/ScintillaCall.h b/include/ScintillaCall.h
index 549c372b5..0e79e37db 100644
--- a/include/ScintillaCall.h
+++ b/include/ScintillaCall.h
@@ -326,6 +326,8 @@ public:
Position FindTextFull(Scintilla::FindOption searchFlags, void *ft);
Position FormatRange(bool draw, void *fr);
Position FormatRangeFull(bool draw, void *fr);
+ void SetChangeHistory(Scintilla::ChangeHistoryOption changeHistory);
+ Scintilla::ChangeHistoryOption ChangeHistory();
Line FirstVisibleLine();
Position GetLine(Line line, char *text);
std::string GetLine(Line line);
diff --git a/include/ScintillaMessages.h b/include/ScintillaMessages.h
index d114db78d..663d6e12c 100644
--- a/include/ScintillaMessages.h
+++ b/include/ScintillaMessages.h
@@ -261,6 +261,8 @@ enum class Message {
FindTextFull = 2196,
FormatRange = 2151,
FormatRangeFull = 2777,
+ SetChangeHistory = 2780,
+ GetChangeHistory = 2781,
GetFirstVisibleLine = 2152,
GetLine = 2153,
GetLineCount = 2154,
diff --git a/include/ScintillaTypes.h b/include/ScintillaTypes.h
index fe4b06032..962542011 100644
--- a/include/ScintillaTypes.h
+++ b/include/ScintillaTypes.h
@@ -90,6 +90,10 @@ enum class MarkerSymbol {
};
enum class MarkerOutline {
+ HistoryRevertedToOrigin = 21,
+ HistorySaved = 22,
+ HistoryModified = 23,
+ HistoryRevertedToModified = 24,
FolderEnd = 25,
FolderOpenMid = 26,
FolderMidTail = 27,
@@ -219,7 +223,15 @@ enum class IndicatorNumbers {
Container = 8,
Ime = 32,
ImeMax = 35,
- Max = 35,
+ HistoryRevertedToOriginInsertion = 36,
+ HistoryRevertedToOriginDeletion = 37,
+ HistorySavedInsertion = 38,
+ HistorySavedDeletion = 39,
+ HistoryModifiedInsertion = 40,
+ HistoryModifiedDeletion = 41,
+ HistoryRevertedToModifiedInsertion = 42,
+ HistoryRevertedToModifiedDeletion = 43,
+ Max = 43,
};
enum class IndicValue {
@@ -263,6 +275,13 @@ enum class FindOption {
Cxx11RegEx = 0x00800000,
};
+enum class ChangeHistoryOption {
+ Disabled = 0,
+ Enabled = 1,
+ Markers = 2,
+ Indicators = 4,
+};
+
enum class FoldLevel {
None = 0x0,
Base = 0x400,
diff --git a/qt/ScintillaEdit/ScintillaEdit.pro b/qt/ScintillaEdit/ScintillaEdit.pro
index 35d613236..a9cf2a42f 100644
--- a/qt/ScintillaEdit/ScintillaEdit.pro
+++ b/qt/ScintillaEdit/ScintillaEdit.pro
@@ -47,6 +47,7 @@ SOURCES += \
../../src/CharClassify.cxx \
../../src/CharacterType.cxx \
../../src/CharacterCategoryMap.cxx \
+ ../../src/ChangeHistory.cxx \
../../src/CellBuffer.cxx \
../../src/CaseFolder.cxx \
../../src/CaseConvert.cxx \
diff --git a/qt/ScintillaEditBase/ScintillaEditBase.pro b/qt/ScintillaEditBase/ScintillaEditBase.pro
index 5edc95ccf..9e72de64b 100644
--- a/qt/ScintillaEditBase/ScintillaEditBase.pro
+++ b/qt/ScintillaEditBase/ScintillaEditBase.pro
@@ -44,6 +44,7 @@ SOURCES += \
../../src/CharClassify.cxx \
../../src/CharacterType.cxx \
../../src/CharacterCategoryMap.cxx \
+ ../../src/ChangeHistory.cxx \
../../src/CellBuffer.cxx \
../../src/CaseFolder.cxx \
../../src/CaseConvert.cxx \
@@ -78,6 +79,7 @@ HEADERS += \
../../src/CharClassify.h \
../../src/CharacterType.h \
../../src/CharacterCategoryMap.h \
+ ../../src/ChangeHistory.h \
../../src/CellBuffer.h \
../../src/CaseFolder.h \
../../src/CaseConvert.h \
diff --git a/scripts/HeaderOrder.txt b/scripts/HeaderOrder.txt
index 9001736ed..b47057e7b 100644
--- a/scripts/HeaderOrder.txt
+++ b/scripts/HeaderOrder.txt
@@ -119,6 +119,7 @@
#include "RunStyles.h"
#include "SparseVector.h"
#include "ContractionState.h"
+#include "ChangeHistory.h"
#include "CellBuffer.h"
#include "PerLine.h"
#include "CallTip.h"
diff --git a/src/CellBuffer.cxx b/src/CellBuffer.cxx
index 2a3aed146..be018bbd6 100644
--- a/src/CellBuffer.cxx
+++ b/src/CellBuffer.cxx
@@ -27,6 +27,9 @@
#include "Position.h"
#include "SplitVector.h"
#include "Partitioning.h"
+#include "RunStyles.h"
+#include "SparseVector.h"
+#include "ChangeHistory.h"
#include "CellBuffer.h"
#include "UniConversion.h"
@@ -396,6 +399,11 @@ const char *UndoHistory::AppendAction(ActionType at, Sci::Position position, con
// actions[currentAction - 1].position, actions[currentAction - 1].lenData);
if (currentAction < savePoint) {
savePoint = -1;
+ if (!detach) {
+ detach = currentAction;
+ }
+ } else if (detach && (*detach > currentAction)) {
+ detach = currentAction;
}
int oldCurrentAction = currentAction;
if (currentAction >= 1) {
@@ -503,12 +511,29 @@ void UndoHistory::DeleteUndoHistory() {
void UndoHistory::SetSavePoint() noexcept {
savePoint = currentAction;
+ detach.reset();
}
bool UndoHistory::IsSavePoint() const noexcept {
return savePoint == currentAction;
}
+bool UndoHistory::BeforeSavePoint() const noexcept {
+ return (savePoint < 0) || (savePoint > currentAction);
+}
+
+bool UndoHistory::BeforeReachableSavePoint() const noexcept {
+ return (savePoint >= 0) && !detach && (savePoint > currentAction);
+}
+
+bool UndoHistory::AfterSavePoint() const noexcept {
+ return (savePoint >= 0) && (savePoint <= currentAction);
+}
+
+bool UndoHistory::AfterDetachPoint() const noexcept {
+ return detach && (*detach < currentAction);
+}
+
void UndoHistory::TentativeStart() {
tentativePoint = currentAction;
}
@@ -682,6 +707,9 @@ const char *CellBuffer::InsertString(Sci::Position position, const char *s, Sci:
}
BasicInsertString(position, s, insertLength);
+ if (changeHistory) {
+ changeHistory->Insert(position, insertLength, collectingUndo, uh.BeforeReachableSavePoint());
+ }
}
return data;
}
@@ -730,6 +758,11 @@ const char *CellBuffer::DeleteChars(Sci::Position position, Sci::Position delete
data = uh.AppendAction(ActionType::remove, position, data, deleteLength, startSequence);
}
+ if (changeHistory) {
+ changeHistory->DeleteRangeSavingHistory(position, deleteLength,
+ uh.BeforeReachableSavePoint(), uh.AfterDetachPoint());
+ }
+
BasicDeleteChars(position, deleteLength);
}
return data;
@@ -845,6 +878,9 @@ bool CellBuffer::HasStyles() const noexcept {
void CellBuffer::SetSavePoint() {
uh.SetSavePoint();
+ if (changeHistory) {
+ changeHistory->SetSavePoint();
+ }
}
bool CellBuffer::IsSavePoint() const noexcept {
@@ -1304,14 +1340,24 @@ const Action &CellBuffer::GetUndoStep() const {
void CellBuffer::PerformUndoStep() {
const Action &actionStep = uh.GetUndoStep();
+ if (changeHistory && uh.BeforeSavePoint()) {
+ changeHistory->StartReversion();
+ }
if (actionStep.at == ActionType::insert) {
if (substance.Length() < actionStep.lenData) {
throw std::runtime_error(
"CellBuffer::PerformUndoStep: deletion must be less than document length.");
}
+ if (changeHistory) {
+ changeHistory->DeleteRange(actionStep.position, actionStep.lenData,
+ uh.BeforeSavePoint() && !uh.AfterDetachPoint());
+ }
BasicDeleteChars(actionStep.position, actionStep.lenData);
} else if (actionStep.at == ActionType::remove) {
BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData);
+ if (changeHistory) {
+ changeHistory->UndoDeleteStep(actionStep.position, actionStep.lenData, uh.AfterDetachPoint());
+ }
}
uh.CompletedUndoStep();
}
@@ -1332,9 +1378,57 @@ void CellBuffer::PerformRedoStep() {
const Action &actionStep = uh.GetRedoStep();
if (actionStep.at == ActionType::insert) {
BasicInsertString(actionStep.position, actionStep.data.get(), actionStep.lenData);
+ if (changeHistory) {
+ changeHistory->Insert(actionStep.position, actionStep.lenData, collectingUndo,
+ uh.BeforeSavePoint() && !uh.AfterDetachPoint());
+ }
} else if (actionStep.at == ActionType::remove) {
+ if (changeHistory) {
+ changeHistory->DeleteRangeSavingHistory(actionStep.position, actionStep.lenData,
+ uh.BeforeReachableSavePoint(), uh.AfterDetachPoint());
+ }
BasicDeleteChars(actionStep.position, actionStep.lenData);
}
+ if (changeHistory && uh.AfterSavePoint()) {
+ changeHistory->EndReversion();
+ }
uh.CompletedRedoStep();
}
+void CellBuffer::ChangeHistorySet(bool set) {
+ if (set) {
+ if (!changeHistory) {
+ changeHistory = std::make_unique<ChangeHistory>(Length());
+ }
+ } else {
+ changeHistory.reset();
+ }
+}
+
+int CellBuffer::EditionAt(Sci::Position pos) const noexcept {
+ if (changeHistory) {
+ return changeHistory->EditionAt(pos);
+ }
+ return 0;
+}
+
+Sci::Position CellBuffer::EditionEndRun(Sci::Position pos) const noexcept {
+ if (changeHistory) {
+ return changeHistory->EditionEndRun(pos);
+ }
+ return Length();
+}
+
+unsigned int CellBuffer::EditionDeletesAt(Sci::Position pos) const noexcept {
+ if (changeHistory) {
+ return changeHistory->EditionDeletesAt(pos);
+ }
+ return 0;
+}
+
+Sci::Position CellBuffer::EditionNextDelete(Sci::Position pos) const noexcept {
+ if (changeHistory) {
+ return changeHistory->EditionNextDelete(pos);
+ }
+ return Length() + 1;
+}
diff --git a/src/CellBuffer.h b/src/CellBuffer.h
index 727e14944..7f0b87c4d 100644
--- a/src/CellBuffer.h
+++ b/src/CellBuffer.h
@@ -20,6 +20,7 @@ public:
virtual void RemoveLine(Sci::Line line)=0;
};
+class ChangeHistory;
/**
* The line vector contains information about each of the lines in a cell buffer.
*/
@@ -53,6 +54,7 @@ class UndoHistory {
int undoSequenceDepth;
int savePoint;
int tentativePoint;
+ std::optional<int> detach;
void EnsureUndoRoom();
@@ -70,6 +72,10 @@ public:
/// the buffer was saved. Undo and redo can move over the save point.
void SetSavePoint() noexcept;
bool IsSavePoint() const noexcept;
+ bool BeforeSavePoint() const noexcept;
+ bool BeforeReachableSavePoint() const noexcept;
+ bool AfterSavePoint() const noexcept;
+ bool AfterDetachPoint() const noexcept;
// Tentative actions are used for input composition so that it can be undone cleanly
void TentativeStart();
@@ -133,6 +139,8 @@ private:
bool collectingUndo;
UndoHistory uh;
+ std::unique_ptr<ChangeHistory> changeHistory;
+
std::unique_ptr<ILineVector> plv;
bool UTF8LineEndOverlaps(Sci::Position position) const noexcept;
@@ -224,6 +232,12 @@ public:
int StartRedo();
const Action &GetRedoStep() const;
void PerformRedoStep();
+
+ void ChangeHistorySet(bool set);
+ [[nodiscard]] int EditionAt(Sci::Position pos) const noexcept;
+ [[nodiscard]] Sci::Position EditionEndRun(Sci::Position pos) const noexcept;
+ [[nodiscard]] unsigned int EditionDeletesAt(Sci::Position pos) const noexcept;
+ [[nodiscard]] Sci::Position EditionNextDelete(Sci::Position pos) const noexcept;
};
}
diff --git a/src/ChangeHistory.cxx b/src/ChangeHistory.cxx
new file mode 100644
index 000000000..4c771a93c
--- /dev/null
+++ b/src/ChangeHistory.cxx
@@ -0,0 +1,422 @@
+// Scintilla source code edit control
+/** @file ChangeHistory.cxx
+ ** Manages a history of changes in a document.
+ **/
+// Copyright 2022 by Neil Hodgson <neilh@scintilla.org>
+// The License.txt file describes the conditions under which this software may be distributed.
+
+#include <cstddef>
+#include <cstdlib>
+#include <cassert>
+
+#include <stdexcept>
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <memory>
+
+#include "ScintillaTypes.h"
+
+#include "Debugging.h"
+
+#include "Position.h"
+#include "SplitVector.h"
+#include "Partitioning.h"
+#include "RunStyles.h"
+#include "SparseVector.h"
+#include "ChangeHistory.h"
+
+namespace Scintilla::Internal {
+
+void ChangeStack::Clear() noexcept {
+ steps.clear();
+ insertions.clear();
+}
+
+void ChangeStack::AddStep() {
+ steps.push_back(0);
+}
+
+void ChangeStack::PushDeletion(Sci::Position positionDeletion, int edition) {
+ steps.back()++;
+ insertions.push_back({ positionDeletion, 0, edition, InsertionSpan::Direction::deletion });
+}
+
+void ChangeStack::PushInsertion(Sci::Position positionInsertion, Sci::Position length, int edition) {
+ steps.back()++;
+ insertions.push_back({ positionInsertion, length, edition, InsertionSpan::Direction::insertion });
+};
+
+size_t ChangeStack::PopStep() noexcept {
+ const size_t spans = steps.back();
+ steps.pop_back();
+ return spans;
+}
+
+InsertionSpan ChangeStack::PopSpan() noexcept {
+ const InsertionSpan span = insertions.back();
+ insertions.pop_back();
+ return span;
+}
+
+void ChangeStack::SetSavePoint() noexcept {
+ // Switch changeUnsaved to changeSaved
+ for (InsertionSpan &x : insertions) {
+ if (x.edition == changeModified) {
+ x.edition = changeSaved;
+ }
+ }
+}
+
+void ChangeLog::Clear(Sci::Position length) {
+ changeStack.Clear();
+ insertEdition.DeleteAll();
+ deleteEdition.DeleteAll();
+ InsertSpace(0, length);
+}
+
+void ChangeLog::InsertSpace(Sci::Position position, Sci::Position insertLength) {
+ assert(insertEdition.Length() == deleteEdition.Length());
+ insertEdition.InsertSpace(position, insertLength);
+ deleteEdition.InsertSpace(position, insertLength);
+}
+
+void ChangeLog::DeleteRange(Sci::Position position, Sci::Position deleteLength) {
+ insertEdition.DeleteRange(position, deleteLength);
+ const EditionSetOwned &editions = deleteEdition.ValueAt(position);
+ if (editions) {
+ const EditionSet savedEditions = *editions;
+ deleteEdition.DeleteRange(position, deleteLength);
+ EditionSetOwned reset = std::make_unique<EditionSet>(savedEditions);
+ deleteEdition.SetValueAt(position, std::move(reset));
+ } else {
+ deleteEdition.DeleteRange(position, deleteLength);
+ }
+ assert(insertEdition.Length() == deleteEdition.Length());
+}
+
+void ChangeLog::Insert(Sci::Position start, Sci::Position length, int edition) {
+ insertEdition.FillRange(start, edition, length);
+}
+
+void ChangeLog::CollapseRange(Sci::Position position, Sci::Position deleteLength) {
+ const Sci::Position positionMax = position + deleteLength;
+ Sci::Position positionDeletion = position + 1;
+ while (positionDeletion <= positionMax) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(positionDeletion);
+ if (editions) {
+ for (const int ed : *editions) {
+ PushDeletionAt(position, ed);
+ }
+ EditionSetOwned empty;
+ deleteEdition.SetValueAt(positionDeletion, std::move(empty));
+ }
+ positionDeletion = deleteEdition.PositionNext(positionDeletion);
+ }
+}
+
+void ChangeLog::PushDeletionAt(Sci::Position position, int edition) {
+ if (!deleteEdition.ValueAt(position)) {
+ deleteEdition.SetValueAt(position, std::make_unique<EditionSet>());
+ }
+ deleteEdition.ValueAt(position)->push_back(edition);
+}
+
+void ChangeLog::InsertFrontDeletionAt(Sci::Position position, int edition) {
+ if (!deleteEdition.ValueAt(position)) {
+ deleteEdition.SetValueAt(position, std::make_unique<EditionSet>());
+ }
+ const EditionSetOwned &editions = deleteEdition.ValueAt(position);
+ editions->insert(editions->begin(), edition);
+}
+
+void ChangeLog::SaveRange(Sci::Position position, Sci::Position length) {
+ // Save insertEdition range into undo stack
+ changeStack.AddStep();
+ Sci::Position positionInsertion = position;
+ const ptrdiff_t editionStart = insertEdition.ValueAt(positionInsertion);
+ if (editionStart == 0) {
+ positionInsertion = insertEdition.EndRun(positionInsertion);
+ }
+ const Sci::Position positionMax = position + length;
+ while (positionInsertion < positionMax) {
+ const Sci::Position positionEndInsertion = insertEdition.EndRun(positionInsertion);
+ changeStack.PushInsertion(positionInsertion, std::min(positionEndInsertion, positionMax) - positionInsertion,
+ insertEdition.ValueAt(positionInsertion));
+ positionInsertion = insertEdition.EndRun(positionEndInsertion);
+ }
+ Sci::Position positionDeletion = position + 1;
+ while (positionDeletion <= positionMax) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(positionDeletion);
+ if (editions) {
+ for (const int ed : *editions) {
+ changeStack.PushDeletion(positionDeletion, ed);
+ }
+ }
+ positionDeletion = deleteEdition.PositionNext(positionDeletion);
+ }
+}
+
+void ChangeLog::PopDeletion(Sci::Position position, Sci::Position deleteLength) {
+ // Just performed InsertSpace(position, deleteLength) so *this* element in
+ // deleteEdition moved forward by deleteLength
+ EditionSetOwned eso = deleteEdition.Extract(position + deleteLength);
+ deleteEdition.SetValueAt(position, std::move(eso));
+ const EditionSetOwned &editions = deleteEdition.ValueAt(position);
+ assert(editions);
+ editions->pop_back();
+ const size_t inserts = changeStack.PopStep();
+ for (size_t i = 0; i < inserts; i++) {
+ const InsertionSpan span = changeStack.PopSpan();
+ if (span.direction == InsertionSpan::Direction::insertion) {
+ insertEdition.FillRange(span.start, span.edition, span.length);
+ } else {
+ assert(editions);
+ assert(editions->back() == span.edition);
+ editions->pop_back();
+ InsertFrontDeletionAt(span.start, span.edition);
+ }
+ }
+
+ if (editions->empty()) {
+ deleteEdition.SetValueAt(position, EditionSetOwned{});
+ }
+}
+
+void ChangeLog::SaveHistoryForDelete(Sci::Position position, Sci::Position deleteLength) {
+ assert(position >= 0);
+ assert(deleteLength >= 0);
+ assert(position + deleteLength <= Length());
+ SaveRange(position, deleteLength);
+ CollapseRange(position, deleteLength);
+}
+
+void ChangeLog::DeleteRangeSavingHistory(Sci::Position position, Sci::Position deleteLength) {
+ SaveHistoryForDelete(position, deleteLength);
+ DeleteRange(position, deleteLength);
+}
+
+void ChangeLog::SetSavePoint() {
+ // Switch changeUnsaved to changeSaved
+ changeStack.SetSavePoint();
+
+ const Sci::Position length = insertEdition.Length();
+
+ for (Sci::Position startRun = 0; startRun < length;) {
+ const Sci::Position endRun = insertEdition.EndRun(startRun);
+ if (insertEdition.ValueAt(startRun) == changeModified) {
+ insertEdition.FillRange(startRun, changeSaved, endRun - startRun);
+ }
+ startRun = endRun;
+ }
+
+ for (Sci::Position positionDeletion = 0; positionDeletion <= length;) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(positionDeletion);
+ if (editions) {
+ for (int &ed : *editions) {
+ if (ed == changeModified) {
+ ed = changeSaved;
+ }
+ }
+ }
+ positionDeletion = deleteEdition.PositionNext(positionDeletion);
+ }
+}
+
+Sci::Position ChangeLog::Length() const noexcept {
+ return insertEdition.Length();
+}
+
+size_t ChangeLog::DeletionCount(Sci::Position start, Sci::Position length) const noexcept {
+ const Sci::Position end = start + length;
+ size_t count = 0;
+ while (start <= end) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(start);
+ if (editions) {
+ count += editions->size();
+ }
+ start = deleteEdition.PositionNext(start);
+ }
+ return count;
+}
+
+void ChangeLog::Check() const noexcept {
+ assert(insertEdition.Length() == deleteEdition.Length());
+}
+
+ChangeHistory::ChangeHistory(Sci::Position length) {
+ changeLog.Clear(length);
+}
+
+void ChangeHistory::Insert(Sci::Position position, Sci::Position insertLength, bool collectingUndo, bool beforeSave) {
+ Check();
+ changeLog.InsertSpace(position, insertLength);
+ const int edition = collectingUndo ? (beforeSave ? changeSaved : changeModified) :
+ changeOriginal;
+ changeLog.Insert(position, insertLength, edition);
+ if (changeLogReversions) {
+ changeLogReversions->InsertSpace(position, insertLength);
+ if (beforeSave) {
+ changeLogReversions->PopDeletion(position, insertLength);
+ }
+ }
+ Check();
+}
+
+void ChangeHistory::DeleteRange(Sci::Position position, Sci::Position deleteLength, bool reverting) {
+ Check();
+ assert(DeletionCount(position, deleteLength-1) == 0);
+ changeLog.DeleteRange(position, deleteLength);
+ if (changeLogReversions) {
+ changeLogReversions->DeleteRangeSavingHistory(position, deleteLength);
+ if (reverting) {
+ changeLogReversions->PushDeletionAt(position, 1);
+ }
+ }
+ Check();
+}
+
+void ChangeHistory::DeleteRangeSavingHistory(Sci::Position position, Sci::Position deleteLength, bool beforeSave, bool isDetached) {
+ changeLog.DeleteRangeSavingHistory(position, deleteLength);
+ changeLog.PushDeletionAt(position, beforeSave ? changeSaved : changeModified);
+ if (changeLogReversions) {
+ if (isDetached) {
+ changeLogReversions->SaveHistoryForDelete(position, deleteLength);
+ }
+ changeLogReversions->DeleteRange(position, deleteLength);
+ }
+ Check();
+}
+
+void ChangeHistory::StartReversion() {
+ if (!changeLogReversions) {
+ changeLogReversions = std::make_unique<ChangeLog>();
+ changeLogReversions->Clear(changeLog.Length());
+ }
+ Check();
+}
+
+void ChangeHistory::EndReversion() noexcept {
+ changeLogReversions.reset();
+ Check();
+}
+
+void ChangeHistory::SetSavePoint() {
+ changeLog.SetSavePoint();
+ EndReversion();
+}
+
+void ChangeHistory::UndoDeleteStep(Sci::Position position, Sci::Position deleteLength, bool isDetached) {
+ Check();
+ changeLog.InsertSpace(position, deleteLength);
+ changeLog.PopDeletion(position, deleteLength);
+ if (changeLogReversions) {
+ changeLogReversions->InsertSpace(position, deleteLength);
+ if (!isDetached) {
+ changeLogReversions->Insert(position, deleteLength, 1);
+ }
+ }
+ Check();
+}
+
+Sci::Position ChangeHistory::Length() const noexcept {
+ return changeLog.Length();
+}
+
+void ChangeHistory::SetEpoch(int epoch) noexcept {
+ historicEpoch = epoch;
+}
+
+void ChangeHistory::EditionCreateHistory(Sci::Position start, Sci::Position length) {
+ if (start <= changeLog.Length()) {
+ if (length) {
+ changeLog.insertEdition.FillRange(start, historicEpoch, length);
+ } else {
+ changeLog.PushDeletionAt(start, historicEpoch);
+ }
+ }
+}
+
+// Editions:
+// <0 History
+// 0 Original unchanged
+// 1 Reverted to origin
+// 2 Saved
+// 3 Unsaved
+// 4 Reverted to change
+int ChangeHistory::EditionAt(Sci::Position pos) const noexcept {
+ const int edition = changeLog.insertEdition.ValueAt(pos);
+ if (changeLogReversions) {
+ const int editionReversion = changeLogReversions->insertEdition.ValueAt(pos);
+ if (editionReversion) {
+ if (edition < 0)
+ return 1;
+ return edition ? 4 : 1;
+ }
+ }
+ return edition;
+}
+
+Sci::Position ChangeHistory::EditionEndRun(Sci::Position pos) const noexcept {
+ if (changeLogReversions) {
+ assert(changeLogReversions->Length() == changeLog.Length());
+ const Sci::Position nextReversion = changeLogReversions->insertEdition.EndRun(pos);
+ const Sci::Position next = changeLog.insertEdition.EndRun(pos);
+ return std::min(next, nextReversion);
+ }
+ return changeLog.insertEdition.EndRun(pos);
+}
+
+// Produce a 4-bit value from the deletions at a position
+unsigned int ChangeHistory::EditionDeletesAt(Sci::Position pos) const noexcept {
+ unsigned int editionSet = 0;
+ const EditionSetOwned &editionSetDeletions = changeLog.deleteEdition.ValueAt(pos);
+ if (editionSetDeletions) {
+ for (const unsigned int ed : *editionSetDeletions) {
+ editionSet = editionSet | (1u << (ed-1));
+ }
+ }
+ if (changeLogReversions) {
+ const EditionSetOwned &editionSetReversions = changeLogReversions->deleteEdition.ValueAt(pos);
+ if (editionSetReversions) {
+ // If there is no saved or modified -> revertedToOrigin
+ if (!(editionSet & (bitSaved | bitModified))) {
+ editionSet = editionSet | bitRevertedToOriginal;
+ } else {
+ editionSet = editionSet | bitRevertedToModified;
+ }
+ }
+ }
+ return editionSet;
+}
+
+Sci::Position ChangeHistory::EditionNextDelete(Sci::Position pos) const noexcept {
+ const Sci::Position next = changeLog.deleteEdition.PositionNext(pos);
+ if (changeLogReversions) {
+ const Sci::Position nextReversion = changeLogReversions->deleteEdition.PositionNext(pos);
+ return std::min(next, nextReversion);
+ }
+ return next;
+}
+
+size_t ChangeHistory::DeletionCount(Sci::Position start, Sci::Position length) const noexcept {
+ return changeLog.DeletionCount(start, length);
+}
+
+EditionSet ChangeHistory::DeletionsAt(Sci::Position pos) const {
+ const EditionSetOwned &editions = changeLog.deleteEdition.ValueAt(pos);
+ if (editions) {
+ return *editions;
+ }
+ return {};
+}
+
+void ChangeHistory::Check() noexcept {
+ changeLog.Check();
+ if (changeLogReversions) {
+ changeLogReversions->Check();
+ assert(changeLogReversions->Length() == changeLog.Length());
+ }
+}
+
+}
diff --git a/src/ChangeHistory.h b/src/ChangeHistory.h
new file mode 100644
index 000000000..8a6e745dc
--- /dev/null
+++ b/src/ChangeHistory.h
@@ -0,0 +1,112 @@
+// Scintilla source code edit control
+/** @file ChangeHistory.h
+ ** Manages a history of changes in a document.
+ **/
+// Copyright 2022 by Neil Hodgson <neilh@scintilla.org>
+// The License.txt file describes the conditions under which this software may be distributed.
+
+#ifndef CHANGEHISTORY_H
+#define CHANGEHISTORY_H
+
+namespace Scintilla::Internal {
+
+constexpr int changeOriginal = 0;
+constexpr int changeRevertedOriginal = 1;
+constexpr int changeSaved = 2;
+constexpr int changeModified = 3;
+constexpr int changeRevertedToChange = 4;
+
+// As bit flags
+constexpr unsigned int bitRevertedToOriginal = 1;
+constexpr unsigned int bitSaved = 2;
+constexpr unsigned int bitModified = 4;
+constexpr unsigned int bitRevertedToModified = 8;
+
+struct InsertionSpan {
+ Sci::Position start;
+ Sci::Position length;
+ int edition;
+ enum class Direction { insertion, deletion } direction;
+};
+
+using EditionSet = std::vector<int>;
+using EditionSetOwned = std::unique_ptr<EditionSet>;
+
+class ChangeStack {
+ std::vector<size_t> steps;
+ std::vector<InsertionSpan> insertions;
+public:
+ void Clear() noexcept;
+ void AddStep();
+ void PushDeletion(Sci::Position positionDeletion, int edition);
+ void PushInsertion(Sci::Position positionInsertion, Sci::Position length, int edition);
+ [[nodiscard]] size_t PopStep() noexcept;
+ [[nodiscard]] InsertionSpan PopSpan() noexcept;
+ void SetSavePoint() noexcept;
+};
+
+struct ChangeLog {
+ ChangeStack changeStack;
+ RunStyles<Sci::Position, int> insertEdition;
+ SparseVector<EditionSetOwned> deleteEdition;
+
+ void Clear(Sci::Position length);
+ void InsertSpace(Sci::Position position, Sci::Position insertLength);
+ void DeleteRange(Sci::Position position, Sci::Position deleteLength);
+ void Insert(Sci::Position start, Sci::Position length, int edition);
+ void CollapseRange(Sci::Position position, Sci::Position deleteLength);
+ void PushDeletionAt(Sci::Position position, int edition);
+ void InsertFrontDeletionAt(Sci::Position position, int edition);
+ void SaveRange(Sci::Position position, Sci::Position length);
+ void PopDeletion(Sci::Position position, Sci::Position deleteLength);
+ void SaveHistoryForDelete(Sci::Position position, Sci::Position deleteLength);
+ void DeleteRangeSavingHistory(Sci::Position position, Sci::Position deleteLength);
+ void SetSavePoint();
+
+ Sci::Position Length() const noexcept;
+ [[nodiscard]] size_t DeletionCount(Sci::Position start, Sci::Position length) const noexcept;
+ void Check() const noexcept;
+};
+
+enum class ReversionState { clear, reverting, detached };
+
+class ChangeHistory {
+ ChangeLog changeLog;
+ std::unique_ptr<ChangeLog> changeLogReversions;
+ int historicEpoch = -1;
+
+public:
+ ChangeHistory(Sci::Position length=0);
+
+ void Insert(Sci::Position position, Sci::Position insertLength, bool collectingUndo, bool beforeSave);
+ void DeleteRange(Sci::Position position, Sci::Position deleteLength, bool reverting);
+ void DeleteRangeSavingHistory(Sci::Position position, Sci::Position deleteLength, bool beforeSave, bool isDetached);
+
+ void StartReversion();
+ void EndReversion() noexcept;
+
+ void SetSavePoint();
+
+ void UndoDeleteStep(Sci::Position position, Sci::Position deleteLength, bool isDetached);
+
+ [[nodiscard]] Sci::Position Length() const noexcept;
+
+ // Setting up history before this session
+ void SetEpoch(int epoch) noexcept;
+ void EditionCreateHistory(Sci::Position start, Sci::Position length);
+
+ // Queries for drawing
+ [[nodiscard]] int EditionAt(Sci::Position pos) const noexcept;
+ [[nodiscard]] Sci::Position EditionEndRun(Sci::Position pos) const noexcept;
+ [[nodiscard]] unsigned int EditionDeletesAt(Sci::Position pos) const noexcept;
+ [[nodiscard]] Sci::Position EditionNextDelete(Sci::Position pos) const noexcept;
+
+ // Testing - not used by Scintilla
+ [[nodiscard]] size_t DeletionCount(Sci::Position start, Sci::Position length) const noexcept;
+ EditionSet DeletionsAt(Sci::Position pos) const;
+ void Check() noexcept;
+};
+
+}
+
+#endif
diff --git a/src/Document.cxx b/src/Document.cxx
index 2312e3f5a..1c3015c92 100644
--- a/src/Document.cxx
+++ b/src/Document.cxx
@@ -339,8 +339,32 @@ void Document::TentativeUndo() {
}
}
-int Document::GetMark(Sci::Line line) const noexcept {
- return Markers()->MarkValue(line);
+int Document::GetMark(Sci::Line line, bool includeChangeHistory) const {
+ int marksHistory = 0;
+ if (includeChangeHistory) {
+ int marksEdition = 0;
+
+ const Sci::Position start = LineStart(line);
+ const Sci::Position lineNext = LineStart(line + 1);
+ for (Sci::Position position = start; position < lineNext;) {
+ const int edition = EditionAt(position);
+ if (edition) {
+ marksEdition |= 1 << (edition-1);
+ }
+ position = EditionEndRun(position);
+ }
+ const Sci::Position lineEnd = LineEnd(line);
+ for (Sci::Position position = start; position <= lineEnd;) {
+ marksEdition |= EditionDeletesAt(position);
+ position = EditionNextDelete(position);
+ }
+
+ /* Bits: RevertedToOrigin, Saved, Modified, RevertedToModified */
+ constexpr unsigned int editionShift = static_cast<unsigned int>(MarkerOutline::HistoryRevertedToOrigin);
+ marksHistory = marksEdition << editionShift;
+ }
+
+ return marksHistory | Markers()->MarkValue(line);
}
Sci::Line Document::MarkerNext(Sci::Line lineStart, int mask) const noexcept {
diff --git a/src/Document.h b/src/Document.h
index e406118a7..4bb7cde15 100644
--- a/src/Document.h
+++ b/src/Document.h
@@ -383,6 +383,12 @@ public:
void TentativeUndo();
bool TentativeActive() const noexcept { return cb.TentativeActive(); }
+ void ChangeHistorySet(bool set) { cb.ChangeHistorySet(set); };
+ [[nodiscard]] int EditionAt(Sci::Position pos) const noexcept { return cb.EditionAt(pos); };
+ [[nodiscard]] Sci::Position EditionEndRun(Sci::Position pos) const noexcept { return cb.EditionEndRun(pos); };
+ [[nodiscard]] unsigned int EditionDeletesAt(Sci::Position pos) const noexcept { return cb.EditionDeletesAt(pos); };
+ [[nodiscard]] Sci::Position EditionNextDelete(Sci::Position pos) const noexcept { return cb.EditionNextDelete(pos); };
+
const char * SCI_METHOD BufferPointer() override { return cb.BufferPointer(); }
const char *RangePointer(Sci::Position position, Sci::Position rangeLength) noexcept { return cb.RangePointer(position, rangeLength); }
Sci::Position GapPosition() const noexcept { return cb.GapPosition(); }
@@ -414,7 +420,7 @@ public:
void GetStyleRange(unsigned char *buffer, Sci::Position position, Sci::Position lengthRetrieve) const {
cb.GetStyleRange(buffer, position, lengthRetrieve);
}
- int GetMark(Sci::Line line) const noexcept;
+ int GetMark(Sci::Line line, bool includeChangeHistory) const;
Sci::Line MarkerNext(Sci::Line lineStart, int mask) const noexcept;
int AddMark(Sci::Line line, int markerNum);
void AddMarkSet(Sci::Line line, int valueSet);
diff --git a/src/EditModel.cxx b/src/EditModel.cxx
index e8e5e5d1e..dd441322e 100644
--- a/src/EditModel.cxx
+++ b/src/EditModel.cxx
@@ -128,5 +128,5 @@ InSelection EditModel::LineEndInSelection(Sci::Line lineDoc) const {
}
int EditModel::GetMark(Sci::Line line) const {
- return pdoc->GetMark(line);
+ return pdoc->GetMark(line, FlagSet(changeHistoryOption, ChangeHistoryOption::Markers));
}
diff --git a/src/EditModel.h b/src/EditModel.h
index 48c230f84..d86f41250 100644
--- a/src/EditModel.h
+++ b/src/EditModel.h
@@ -49,6 +49,8 @@ public:
bool hotspotSingleLine;
Sci::Position hoverIndicatorPos;
+ Scintilla::ChangeHistoryOption changeHistoryOption = Scintilla::ChangeHistoryOption::Disabled;
+
// Wrapping support
int wrapWidth;
diff --git a/src/EditView.cxx b/src/EditView.cxx
index c3709f25b..c4768a2a3 100644
--- a/src/EditView.cxx
+++ b/src/EditView.cxx
@@ -1326,6 +1326,45 @@ static void DrawIndicators(Surface *surface, const EditModel &model, const ViewS
}
}
}
+
+ if (FlagSet(model.changeHistoryOption, ChangeHistoryOption::Indicators)) {
+ // Draw editions
+ const int indexHistory = static_cast<int>(IndicatorNumbers::HistoryRevertedToOriginInsertion);
+ {
+ // Draw insertions
+ Sci::Position startPos = posLineStart + lineStart;
+ while (startPos < posLineEnd) {
+ const Range rangeRun(startPos, model.pdoc->EditionEndRun(startPos));
+ const Sci::Position endPos = std::min(rangeRun.end, posLineEnd);
+ const int edition = model.pdoc->EditionAt(startPos);
+ if (edition != 0) {
+ const int indicator = (edition - 1) * 2 + indexHistory;
+ const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(rangeRun.First() + 1, 1);
+ DrawIndicator(indicator, startPos - posLineStart, endPos - posLineStart,
+ surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, Indicator::State::normal,
+ 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
+ }
+ startPos = endPos;
+ }
+ }
+ {
+ // Draw deletions
+ Sci::Position startPos = posLineStart + lineStart;
+ while (startPos <= posLineEnd) {
+ const unsigned int editions = model.pdoc->EditionDeletesAt(startPos);
+ const Sci::Position posSecond = model.pdoc->MovePositionOutsideChar(startPos + 1, 1);
+ for (unsigned int edition=0; edition<4; edition++) {
+ if (editions & (1 << edition)) {
+ const int indicator = edition * 2 + indexHistory + 1;
+ DrawIndicator(indicator, startPos - posLineStart, posSecond - posLineStart,
+ surface, vsDraw, ll, xStart, rcLine, posSecond - posLineStart, subLine, Indicator::State::normal,
+ 1, model.BidirectionalEnabled(), tabWidthMinimumPixels);
+ }
+ }
+ startPos = model.pdoc->EditionNextDelete(startPos);
+ }
+ }
+ }
}
void EditView::DrawFoldDisplayText(Surface *surface, const EditModel &model, const ViewStyle &vsDraw, const LineLayout *ll,
diff --git a/src/Editor.cxx b/src/Editor.cxx
index ccf187c65..c2c31f0d1 100644
--- a/src/Editor.cxx
+++ b/src/Editor.cxx
@@ -2405,6 +2405,9 @@ void Editor::NotifySavePoint(bool isSavePoint) {
NotificationData scn = {};
if (isSavePoint) {
scn.nmhdr.code = Notification::SavePointReached;
+ if (changeHistoryOption != ChangeHistoryOption::Disabled) {
+ Redraw();
+ }
} else {
scn.nmhdr.code = Notification::SavePointLeft;
}
@@ -2733,6 +2736,9 @@ void Editor::NotifyModified(Document *, DocModification mh, void *) {
QueueIdleWork(WorkItems::style, mh.position + mh.length);
}
InvalidateRange(mh.position, mh.position + mh.length);
+ if (FlagSet(changeHistoryOption, ChangeHistoryOption::Markers)) {
+ RedrawSelMargin(pdoc->SciLineFromPosition(mh.position));
+ }
}
}
}
@@ -8349,6 +8355,14 @@ sptr_t Editor::WndProc(Message iMessage, uptr_t wParam, sptr_t lParam) {
case Message::GetGapPosition:
return pdoc->GapPosition();
+ case Message::SetChangeHistory:
+ changeHistoryOption = static_cast<ChangeHistoryOption>(wParam);
+ pdoc->ChangeHistorySet(wParam & 1);
+ break;
+
+ case Message::GetChangeHistory:
+ return static_cast<sptr_t>(changeHistoryOption);
+
case Message::SetExtraAscent:
vs.extraAscent = static_cast<int>(wParam);
InvalidateStyleRedraw();
diff --git a/src/SparseVector.h b/src/SparseVector.h
index f8c35fdc5..09eb1f98d 100644
--- a/src/SparseVector.h
+++ b/src/SparseVector.h
@@ -57,6 +57,22 @@ public:
return empty;
}
}
+ T Extract(Sci::Position position) {
+ // Move value currently at position; clear and remove position; return value.
+ // Doesn't remove position at start or end.
+ assert(position <= Length());
+ const Sci::Position partition = ElementFromPosition(position);
+ assert(partition >= 0);
+ assert(partition <= starts.Partitions());
+ assert(starts.PositionFromPartition(partition) == position);
+ T value = std::move(values.operator[](partition));
+ if ((partition > 0) && (partition < starts.Partitions())) {
+ starts.RemovePartition(partition);
+ values.Delete(partition);
+ }
+ Check();
+ return value;
+ }
template <typename ParamType>
void SetValueAt(Sci::Position position, ParamType &&value) {
assert(position <= Length());
diff --git a/src/ViewStyle.cxx b/src/ViewStyle.cxx
index 25a5f9cad..01b94cca1 100644
--- a/src/ViewStyle.cxx
+++ b/src/ViewStyle.cxx
@@ -104,6 +104,49 @@ ViewStyle::ViewStyle(size_t stylesSize_) :
indicators[1] = Indicator(IndicatorStyle::TT, ColourRGBA(0, 0, 0xff));
indicators[2] = Indicator(IndicatorStyle::Plain, ColourRGBA(0xff, 0, 0));
+ // Reverted to origin
+ constexpr ColourRGBA revertedToOrigin(0x40, 0xA0, 0xBF);
+ // Saved
+ constexpr ColourRGBA saved(0x0, 0xA0, 0x0);
+ // Modified
+ constexpr ColourRGBA modified(0xA0, 0xA0, 0x0);
+ // Reverted to change
+ constexpr ColourRGBA revertedToChange(0xFF, 0x80, 0x0);
+
+ // Edition indicators
+ constexpr size_t indexHistory = static_cast<size_t>(IndicatorNumbers::HistoryRevertedToOriginInsertion);
+
+ indicators[indexHistory+0] = Indicator(IndicatorStyle::StraightBox, ColourRGBA(revertedToOrigin, 0x40));
+ indicators[indexHistory+1] = Indicator(IndicatorStyle::Point, revertedToOrigin);
+ indicators[indexHistory+2] = Indicator(IndicatorStyle::StraightBox, ColourRGBA(saved, 0x40));
+ indicators[indexHistory+3] = Indicator(IndicatorStyle::Point, saved);
+ indicators[indexHistory+4] = Indicator(IndicatorStyle::StraightBox, ColourRGBA(modified, 0x40));
+ indicators[indexHistory+5] = Indicator(IndicatorStyle::Point, modified);
+ indicators[indexHistory+6] = Indicator(IndicatorStyle::StraightBox, ColourRGBA(revertedToChange, 0x40));
+ indicators[indexHistory+7] = Indicator(IndicatorStyle::Point, revertedToChange);
+
+ // Edition markers
+ // Reverted to origin
+ constexpr size_t indexHistoryRevertedToOrigin = static_cast<size_t>(MarkerOutline::HistoryRevertedToOrigin);
+ markers[indexHistoryRevertedToOrigin].back = revertedToOrigin;
+ markers[indexHistoryRevertedToOrigin].fore = revertedToOrigin;
+ markers[indexHistoryRevertedToOrigin].markType = MarkerSymbol::LeftRect;
+ // Saved
+ constexpr size_t indexHistorySaved = static_cast<size_t>(MarkerOutline::HistorySaved);
+ markers[indexHistorySaved].back = saved;
+ markers[indexHistorySaved].fore = saved;
+ markers[indexHistorySaved].markType = MarkerSymbol::LeftRect;
+ // Modified
+ constexpr size_t indexHistoryModified = static_cast<size_t>(MarkerOutline::HistoryModified);
+ markers[indexHistoryModified].back = ColourRGBA(modified, 0x40);
+ markers[indexHistoryModified].fore = modified;
+ markers[indexHistoryModified].markType = MarkerSymbol::LeftRect;
+ // Reverted to change
+ constexpr size_t indexHistoryRevertedToModified = static_cast<size_t>(MarkerOutline::HistoryRevertedToModified);
+ markers[indexHistoryRevertedToModified].back = revertedToChange;
+ markers[indexHistoryRevertedToModified].fore = revertedToChange;
+ markers[indexHistoryRevertedToModified].markType = MarkerSymbol::LeftRect;
+
technology = Technology::Default;
indicatorsDynamic = false;
indicatorsSetFore = false;
diff --git a/test/unit/Sci.natvis b/test/unit/Sci.natvis
index d6ac2b96c..c0bd9d15d 100644
--- a/test/unit/Sci.natvis
+++ b/test/unit/Sci.natvis
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
- <Type Name="Scintilla::SplitVector&lt;*&gt;">
+ <Type Name="Scintilla::Internal::SplitVector&lt;*&gt;">
<DisplayString>{{size = {lengthBody}}}</DisplayString>
<Expand>
<Item Name="[size]">lengthBody</Item>
@@ -12,7 +12,20 @@
</IndexListItems>
</Expand>
</Type>
- <Type Name="Scintilla::Partitioning&lt;*&gt;">
+ <Type Name="Scintilla::Internal::XSparseVector&lt;*&gt;">
+ <DisplayString>{{size = {values->lengthBody}}}</DisplayString>
+ <Expand>
+ <IndexListItems>
+ <Size>values->lengthBody</Size>
+ <ValueNode>starts->body->body[($i&lt;starts->body->part1Length)?$i:$i+starts->body->gapLength]+($i&gt;starts->stepPartition?starts->stepLength:0)</ValueNode>
+ </IndexListItems>
+ <IndexListItems>
+ <Size>values->lengthBody</Size>
+ <ValueNode>values->body->body[($i&lt;values->body->part1Length)?$i:$i+values->body->gapLength]+($i&gt;values->stepPartition?values->stepLength:0)</ValueNode>
+ </IndexListItems>
+ </Expand>
+ </Type>
+ <Type Name="Scintilla::Internal::Partitioning&lt;*&gt;">
<DisplayString>{{size = {body->lengthBody}}}</DisplayString>
<Expand>
<IndexListItems>
diff --git a/test/unit/UnitTester.vcxproj b/test/unit/UnitTester.vcxproj
index 8d41cf383..38b0eb7cc 100644
--- a/test/unit/UnitTester.vcxproj
+++ b/test/unit/UnitTester.vcxproj
@@ -22,32 +22,32 @@
<ProjectGuid>{35688A27-D91B-453A-8A05-65A7F28DEFBF}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>UnitTester</RootNamespace>
- <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+ <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
- <PlatformToolset>v141</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
@@ -74,6 +74,8 @@
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
+ <EnableClangTidyCodeAnalysis>true</EnableClangTidyCodeAnalysis>
+ <CodeAnalysisRuleSet>..\..\..\..\..\Users\Neil\SensibleRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
@@ -153,6 +155,7 @@
<ClCompile Include="..\..\src\CaseConvert.cxx" />
<ClCompile Include="..\..\src\CaseFolder.cxx" />
<ClCompile Include="..\..\src\CellBuffer.cxx" />
+ <ClCompile Include="..\..\src\ChangeHistory.cxx" />
<ClCompile Include="..\..\src\CharacterCategoryMap.cxx" />
<ClCompile Include="..\..\src\CharClassify.cxx" />
<ClCompile Include="..\..\src\ContractionState.cxx" />
diff --git a/test/unit/makefile b/test/unit/makefile
index 89766826f..46747931f 100644
--- a/test/unit/makefile
+++ b/test/unit/makefile
@@ -56,6 +56,7 @@ TESTEDSRC=\
../../src/CaseConvert.cxx \
../../src/CaseFolder.cxx \
../../src/CellBuffer.cxx \
+ ../../src/ChangeHistory.cxx \
../../src/CharacterCategoryMap.cxx \
../../src/CharClassify.cxx \
../../src/ContractionState.cxx \
diff --git a/test/unit/test.mak b/test/unit/test.mak
index 99e4882c2..10a6ebac7 100644
--- a/test/unit/test.mak
+++ b/test/unit/test.mak
@@ -15,6 +15,7 @@ TESTEDSRC=\
../../src/CaseConvert.cxx \
../../src/CaseFolder.cxx \
../../src/CellBuffer.cxx \
+ ../../src/ChangeHistory.cxx \
../../src/CharacterCategoryMap.cxx \
../../src/CharClassify.cxx \
../../src/ContractionState.cxx \
diff --git a/test/unit/testCellBuffer.cxx b/test/unit/testCellBuffer.cxx
index ef7db099b..b6735339a 100644
--- a/test/unit/testCellBuffer.cxx
+++ b/test/unit/testCellBuffer.cxx
@@ -3,6 +3,7 @@
**/
#include <cstddef>
+#include <cassert>
#include <cstring>
#include <stdexcept>
#include <string_view>
@@ -19,6 +20,8 @@
#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
+#include "SparseVector.h"
+#include "ChangeHistory.h"
#include "CellBuffer.h"
#include "catch.hpp"
@@ -37,7 +40,7 @@ TEST_CASE("CellBuffer") {
SECTION("InsertOneLine") {
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(startSequence);
REQUIRE(sLength == cb.Length());
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
@@ -54,7 +57,7 @@ TEST_CASE("CellBuffer") {
const char sText2[] = "Two\nLines";
const Sci::Position sLength2 = static_cast<Sci::Position>(strlen(sText2));
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText2, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText2, sLength2, startSequence);
REQUIRE(startSequence);
REQUIRE(sLength2 == cb.Length());
REQUIRE(memcmp(cpChange, sText2, sLength2) == 0);
@@ -64,7 +67,7 @@ TEST_CASE("CellBuffer") {
REQUIRE(4 == cb.LineStart(1));
REQUIRE(1 == cb.LineFromPosition(5));
REQUIRE(sLength2 == cb.LineStart(2));
- REQUIRE(1 == cb.LineFromPosition(static_cast<int>(sLength)));
+ REQUIRE(1 == cb.LineFromPosition(sLength2));
REQUIRE(cb.CanUndo());
REQUIRE(!cb.CanRedo());
}
@@ -74,7 +77,7 @@ TEST_CASE("CellBuffer") {
cb.SetUndoCollection(false);
REQUIRE(!cb.IsCollectingUndo());
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(!startSequence);
REQUIRE(sLength == cb.Length());
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
@@ -86,7 +89,7 @@ TEST_CASE("CellBuffer") {
const char sTextDeleted[] = "ci";
const char sTextAfterDeletion[] = "Sntilla";
bool startSequence = false;
- const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ const char *cpChange = cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(startSequence);
REQUIRE(sLength == cb.Length());
REQUIRE(memcmp(cpChange, sText, sLength) == 0);
@@ -146,7 +149,7 @@ TEST_CASE("CellBuffer") {
cb.SetReadOnly(true);
REQUIRE(cb.IsReadOnly());
bool startSequence = false;
- cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
+ cb.InsertString(0, sText, sLength, startSequence);
REQUIRE(cb.Length() == 0);
}
@@ -438,3 +441,581 @@ TEST_CASE("CharacterIndex") {
REQUIRE(cb.IndexLineStart(3, LineCharacterIndexType::Utf16) == 5);
}
}
+
+TEST_CASE("ChangeHistory") {
+
+ ChangeHistory il;
+ const EditionSet empty;
+ struct Spanner {
+ Sci::Position position = 0;
+ Sci::Position length = 0;
+ };
+
+ SECTION("Start") {
+ REQUIRE(il.Length() == 0);
+ REQUIRE(il.DeletionCount(0,0) == 0);
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 0);
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 1);
+ }
+
+ SECTION("Some Space") {
+ il.Insert(0, 10, false, true);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0,10) == 0);
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 10);
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 10);
+ REQUIRE(il.EditionDeletesAt(10) == 0);
+ REQUIRE(il.EditionNextDelete(10) == 11);
+ }
+
+ SECTION("An insert") {
+ il.Insert(0, 7, false, true);
+ il.SetSavePoint();
+ il.Insert(2, 3, true, true);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0,10) == 0);
+
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 2);
+ REQUIRE(il.EditionAt(2) == 2);
+ REQUIRE(il.EditionEndRun(2) == 5);
+ REQUIRE(il.EditionAt(5) == 0);
+ REQUIRE(il.EditionEndRun(5) == 10);
+ REQUIRE(il.EditionAt(10) == 0);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 10);
+ REQUIRE(il.EditionDeletesAt(10) == 0);
+ }
+
+ SECTION("A delete") {
+ il.Insert(0, 10, false, true);
+ il.SetSavePoint();
+ il.DeleteRangeSavingHistory(2, 3, true, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0,7) == 1);
+
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 7);
+ REQUIRE(il.EditionAt(7) == 0);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ const EditionSet one{ 2 };
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 7);
+ REQUIRE(il.EditionDeletesAt(7) == 0);
+ }
+
+ SECTION("Insert, delete, and undo") {
+ il.Insert(0, 9, false, true);
+ il.SetSavePoint();
+ il.Insert(3, 1, true, true);
+ REQUIRE(il.EditionEndRun(0) == 3);
+ REQUIRE(il.EditionAt(3) == 2);
+ REQUIRE(il.EditionEndRun(3) == 4);
+ REQUIRE(il.EditionAt(4) == 0);
+
+ il.DeleteRangeSavingHistory(2, 3, true, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0,7) == 1);
+
+ REQUIRE(il.EditionAt(0) == 0);
+ REQUIRE(il.EditionEndRun(0) == 7);
+ REQUIRE(il.EditionAt(7) == 0);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ const EditionSet one{ 2 };
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 7);
+ REQUIRE(il.EditionDeletesAt(7) == 0);
+
+ // Undo in detail (normally inside CellBuffer::PerformUndoStep)
+ il.UndoDeleteStep(2, 3, false);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ // The insertion has reappeared
+ REQUIRE(il.EditionEndRun(0) == 3);
+ REQUIRE(il.EditionAt(3) == 2);
+ REQUIRE(il.EditionEndRun(3) == 4);
+ REQUIRE(il.EditionAt(4) == 0);
+ }
+
+ SECTION("Deletes") {
+ il.Insert(0, 10, false, true);
+ il.SetSavePoint();
+ il.DeleteRangeSavingHistory(2, 3, true, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0,7) == 1);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 7);
+ REQUIRE(il.EditionDeletesAt(7) == 0);
+
+ il.DeleteRangeSavingHistory(2, 1, true, false);
+ REQUIRE(il.Length() == 6);
+ REQUIRE(il.DeletionCount(0,6) == 2);
+
+ REQUIRE(il.EditionDeletesAt(0) == 0);
+ REQUIRE(il.EditionNextDelete(0) == 2);
+ REQUIRE(il.EditionDeletesAt(2) == 2);
+ REQUIRE(il.EditionNextDelete(2) == 6);
+ REQUIRE(il.EditionDeletesAt(6) == 0);
+
+ // Undo in detail (normally inside CellBuffer::PerformUndoStep)
+ il.UndoDeleteStep(2, 1, false);
+ REQUIRE(il.Length() == 7);
+ REQUIRE(il.DeletionCount(0, 7) == 1);
+
+ // Undo in detail (normally inside CellBuffer::PerformUndoStep)
+ il.UndoDeleteStep(2, 3, false);
+ REQUIRE(il.Length() == 10);
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ }
+
+ SECTION("Deletes 101") {
+ // Deletes that hit the start and end permanent positions
+ il.Insert(0, 3, false, true);
+ il.SetSavePoint();
+ REQUIRE(il.DeletionCount(0, 2) == 0);
+ il.DeleteRangeSavingHistory(1, 1, true, false);
+ REQUIRE(il.DeletionCount(0,2) == 1);
+ const EditionSet at1 = {2};
+ REQUIRE(il.DeletionsAt(1) == at1);
+ il.DeleteRangeSavingHistory(1, 1, false, false);
+ REQUIRE(il.DeletionCount(0,1) == 2);
+ const EditionSet at2 = { 2, 3 };
+ REQUIRE(il.DeletionsAt(1) == at2);
+ il.DeleteRangeSavingHistory(0, 1, false, false);
+ const EditionSet at3 = { 2, 3, 3 };
+ REQUIRE(il.DeletionsAt(0) == at3);
+ REQUIRE(il.DeletionCount(0,0) == 3);
+
+ // Undo them
+ il.UndoDeleteStep(0, 1, false);
+ REQUIRE(il.DeletionCount(0, 1) == 2);
+ REQUIRE(il.DeletionsAt(1) == at2);
+ il.UndoDeleteStep(1, 1, false);
+ REQUIRE(il.DeletionCount(0, 2) == 1);
+ REQUIRE(il.DeletionsAt(1) == at1);
+ il.UndoDeleteStep(1, 1, false);
+ REQUIRE(il.DeletionCount(0, 3) == 0);
+ }
+
+ SECTION("Deletes Stack") {
+ std::vector<Spanner> spans = {
+ {5, 1},
+ {4, 3},
+ {1, 1},
+ {1, 1},
+ {0, 1},
+ {0, 3},
+ };
+
+ // Deletes that hit the start and end permanent positions
+ il.Insert(0, 10, false, true);
+ REQUIRE(il.Length() == 10);
+ il.SetSavePoint();
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ for (size_t i = 0; i < std::size(spans); i++) {
+ il.DeleteRangeSavingHistory(spans[i].position, spans[i].length, false, false);
+ }
+ REQUIRE(il.Length() == 0);
+ for (size_t j = 0; j < std::size(spans); j++) {
+ const size_t i = std::size(spans) - j - 1;
+ il.UndoDeleteStep(spans[i].position, spans[i].length, false);
+ }
+ REQUIRE(il.DeletionCount(0, 10) == 0);
+ REQUIRE(il.Length() == 10);
+ }
+}
+
+struct InsertionResult {
+ Sci::Position position;
+ Sci::Position length;
+ int state;
+ bool operator==(const InsertionResult &other) const noexcept {
+ return position == other.position &&
+ length == other.length &&
+ state == other.state;
+ }
+};
+
+std::ostream &operator << (std::ostream &os, InsertionResult const &value) {
+ os << value.position << " " << value.length << " " << value.state;
+ return os;
+}
+
+using Insertions = std::vector<InsertionResult>;
+
+std::ostream &operator << (std::ostream &os, Insertions const &value) {
+ os << "(";
+ for (const InsertionResult &el : value) {
+ os << "(" << el << ") ";
+ }
+ os << ")";
+ return os;
+}
+
+Insertions HistoryInsertions(const CellBuffer &cb) {
+ Insertions result;
+ Sci::Position startPos = 0;
+ while (startPos < cb.Length()) {
+ const Sci::Position endPos = cb.EditionEndRun(startPos);
+ const int ed = cb.EditionAt(startPos);
+ if (ed) {
+ result.push_back({ startPos, endPos - startPos, ed });
+ }
+ startPos = endPos;
+ }
+ return result;
+}
+
+struct DeletionResult {
+ Sci::Position position;
+ int state;
+ bool operator==(const DeletionResult &other) const noexcept {
+ return position == other.position &&
+ state == other.state;
+ }
+};
+
+std::ostream &operator << (std::ostream &os, DeletionResult const &value) {
+ os << value.position << " " << value.state;
+ return os;
+}
+
+using Deletions = std::vector<DeletionResult>;
+
+std::ostream &operator << (std::ostream &os, Deletions const &value) {
+ os << "(";
+ for (const DeletionResult &el : value) {
+ os << "(" << el << ") ";
+ }
+ os << ")";
+ return os;
+}
+
+Deletions HistoryDeletions(const CellBuffer &cb) {
+ Deletions result;
+ Sci::Position positionDeletion = 0;
+ while (positionDeletion <= cb.Length()) {
+ const unsigned int editions = cb.EditionDeletesAt(positionDeletion);
+ if (editions & 1) {
+ result.push_back({ positionDeletion, 1 });
+ }
+ if (editions & 2) {
+ result.push_back({ positionDeletion, 2 });
+ }
+ if (editions & 4) {
+ result.push_back({ positionDeletion, 3 });
+ }
+ if (editions & 8) {
+ result.push_back({ positionDeletion, 4 });
+ }
+ positionDeletion = cb.EditionNextDelete(positionDeletion);
+ }
+ return result;
+}
+
+struct History {
+ Insertions insertions;
+ Deletions deletions;
+ bool operator==(const History &other) const {
+ return insertions == other.insertions &&
+ deletions == other.deletions;
+ }
+};
+
+std::ostream &operator << (std::ostream &os, History const &value) {
+ os << value.insertions << " " << value.deletions;
+ return os;
+}
+
+History HistoryOf(const CellBuffer &cb) {
+ return { HistoryInsertions(cb), HistoryDeletions(cb) };
+}
+
+void UndoBlock(CellBuffer &cb) {
+ const int steps = cb.StartUndo();
+ for (int step = 0; step < steps; step++) {
+ cb.PerformUndoStep();
+ }
+}
+
+void RedoBlock(CellBuffer &cb) {
+ const int steps = cb.StartRedo();
+ for (int step = 0; step < steps; step++) {
+ cb.PerformRedoStep();
+ }
+}
+
+TEST_CASE("CellBufferWithChangeHistory") {
+
+ SECTION("StraightUndoRedoSaveRevertRedo") {
+ CellBuffer cb(true, false);
+ cb.SetUndoCollection(false);
+ std::string sInsert = "abcdefghijklmnopqrstuvwxyz";
+ bool startSequence = false;
+ cb.InsertString(0, sInsert.c_str(), sInsert.length(), startSequence);
+ cb.SetUndoCollection(true);
+ cb.SetSavePoint();
+ cb.ChangeHistorySet(true);
+
+ const History history0 { {}, {} };
+ REQUIRE(HistoryOf(cb) == history0);
+
+ // 1
+ cb.InsertString(4, "_", 1, startSequence);
+ const History history1{ {{4, 1, 3}}, {} };
+ REQUIRE(HistoryOf(cb) == history1);
+
+ // 2
+ cb.DeleteChars(2, 1, startSequence);
+ const History history2{ {{3, 1, 3}},
+ {{2, 3}} };
+ REQUIRE(HistoryOf(cb) == history2);
+
+ // 3
+ cb.InsertString(1, "[!]", 3, startSequence);
+ const History history3{ { {1, 3, 3}, {6, 1, 3} },
+ { {5, 3} } };
+ REQUIRE(HistoryOf(cb) == history3);
+
+ // 4
+ cb.DeleteChars(2, 1, startSequence); // Inside an insertion
+ const History history4{ { {1, 2, 3}, {5, 1, 3} },
+ { {2, 3}, {4, 3} }};
+ REQUIRE(HistoryOf(cb) == history4);
+
+ // 5 Delete all the insertions and deletions
+ cb.DeleteChars(1, 6, startSequence); // Inside an insertion
+ const History history5{ { },
+ { {1, 3} } };
+ REQUIRE(HistoryOf(cb) == history5);
+
+ // Undo all
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history4);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history3);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history2);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history1);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history0);
+
+ // Redo all
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history1);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history2);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history3);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history4);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history5);
+
+ cb.SetSavePoint();
+ const History history5s{ { },
+ { {1, 2} } };
+ REQUIRE(HistoryOf(cb) == history5s);
+
+ // Change past save point
+ cb.InsertString(4, "123", 3, startSequence);
+ const History history6{ { {4, 3, 3} },
+ { {1, 2} } };
+ REQUIRE(HistoryOf(cb) == history6);
+
+ // Undo to save point: same as 5 but with save state instead of unsaved
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history5s);
+
+ // Reverting past save point, similar to 4 but with most saved and
+ // reverted delete at 1
+ UndoBlock(cb); // Reinsert most of original changes
+ const History history4s{ { {1, 2, 4}, {3, 2, 1}, {5, 1, 4}, {6, 1, 1} },
+ { {2, 2}, {4, 2} } };
+ REQUIRE(HistoryOf(cb) == history4s);
+
+ UndoBlock(cb); // Reinsert "!",
+ const History history3s{ { {1, 3, 4}, {4, 2, 1}, {6, 1, 4}, {7, 1, 1} },
+ { {5, 2} } };
+ REQUIRE(HistoryOf(cb) == history3s);
+
+ UndoBlock(cb); // Revert insertion of [!]
+ const History history2s{ { {1, 2, 1}, {3, 1, 4}, {4, 1, 1} },
+ { {1, 1}, {2, 2} } };
+ REQUIRE(HistoryOf(cb) == history2s);
+
+ UndoBlock(cb); // Revert deletion, inserts at 2
+ const History history1s{ { {1, 3, 1}, {4, 1, 4}, {5, 1, 1} },
+ { {1, 1} } };
+ REQUIRE(HistoryOf(cb) == history1s);
+
+ UndoBlock(cb); // Revert insertion of _ at 4, drops middle insertion run
+ // So merges down to 1 insertion
+ const History history0s{ { {1, 4, 1} },
+ { {1, 1}, {4, 1} } };
+ REQUIRE(HistoryOf(cb) == history0s);
+
+ // At origin but with changes from disk
+ // Now redo the steps
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history1s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history2s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history3s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history4s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history5s);
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == history6);
+ }
+
+ SECTION("Detached") {
+ CellBuffer cb(true, false);
+ cb.SetUndoCollection(false);
+ std::string sInsert = "abcdefghijklmnopqrstuvwxyz";
+ bool startSequence = false;
+ cb.InsertString(0, sInsert.c_str(), sInsert.length(), startSequence);
+ cb.SetUndoCollection(true);
+ cb.SetSavePoint();
+ cb.ChangeHistorySet(true);
+
+ const History history0{ {}, {} };
+ REQUIRE(HistoryOf(cb) == history0);
+
+ // 1
+ cb.InsertString(4, "_", 1, startSequence);
+ const History history1{ {{4, 1, 3}}, {} };
+ REQUIRE(HistoryOf(cb) == history1);
+
+ // 2
+ cb.DeleteChars(2, 1, startSequence);
+ const History history2{ {{3, 1, 3}},
+ {{2, 3}} };
+ REQUIRE(HistoryOf(cb) == history2);
+
+ cb.SetSavePoint();
+
+ UndoBlock(cb);
+ const History history1s{ {{2, 1, 1}, {4, 1, 2}}, {} };
+ REQUIRE(HistoryOf(cb) == history1s);
+
+ cb.InsertString(6, "()", 2, startSequence);
+ const History detached2{ {{2, 1, 1}, {4, 1, 2}, {6, 2, 3}}, {} };
+ REQUIRE(HistoryOf(cb) == detached2);
+
+ cb.DeleteChars(9, 3, startSequence);
+ const History detached3{ {{2, 1, 1}, {4, 1, 2}, {6, 2, 3}}, {{9,3}} };
+ REQUIRE(HistoryOf(cb) == detached3);
+
+ UndoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached2);
+ UndoBlock(cb);
+ const History detached1{ {{2, 1, 1}, {4, 1, 2}}, {} };
+ REQUIRE(HistoryOf(cb) == detached1);
+ UndoBlock(cb);
+ const History detached0{ {{2, 1, 1}}, {{4,1}} };
+ REQUIRE(HistoryOf(cb) == detached0);
+ REQUIRE(!cb.CanUndo());
+
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached1);
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached2);
+ RedoBlock(cb);
+ REQUIRE(HistoryOf(cb) == detached3);
+ }
+}
+
+namespace {
+
+// Implement low quality reproducible pseudo-random numbers.
+// Pseudo-random algorithm based on R. G. Dromey "How to Solve it by Computer" page 122.
+
+class RandomSequence {
+ static constexpr int mult = 109;
+ static constexpr int incr = 853;
+ static constexpr int modulus = 4096;
+ int randomValue = 127;
+public:
+ int Next() noexcept {
+ randomValue = (mult * randomValue + incr) % modulus;
+ return randomValue;
+ }
+};
+
+}
+
+#if 1
+TEST_CASE("CellBufferLong") {
+
+ // Call methods on CellBuffer pseudo-randomly trying to trigger assertion failures
+
+ CellBuffer cb(true, false);
+
+ SECTION("Random") {
+ RandomSequence rseq;
+ for (size_t i = 0l; i < 20000; i++) {
+ const int r = rseq.Next() % 10;
+ if (r <= 2) { // 30%
+ // Insert text
+ const int pos = rseq.Next() % (cb.Length() + 1);
+ const int len = rseq.Next() % 10 + 1;
+ std::string sInsert;
+ for (int j = 0; j < len; j++) {
+ sInsert.push_back(static_cast<char>('a' + j));
+ }
+ bool startSequence = false;
+ cb.InsertString(pos, sInsert.c_str(), len, startSequence);
+ } else if (r <= 5) { // 30%
+ // Delete Text
+ const Sci::Position pos = rseq.Next() % (cb.Length() + 1);
+ const int len = rseq.Next() % 10 + 1;
+ if (pos + len <= cb.Length()) {
+ bool startSequence = false;
+ cb.DeleteChars(pos, len, startSequence);
+ }
+ } else if (r <= 8) { // 30%
+ // Undo or redo
+ const bool undo = rseq.Next() % 2 == 1;
+ if (undo) {
+ UndoBlock(cb);
+ } else {
+ RedoBlock(cb);
+ }
+ } else { // 10%
+ // Save
+ cb.SetSavePoint();
+ }
+ }
+ }
+}
+#endif \ No newline at end of file
diff --git a/win32/Scintilla.vcxproj b/win32/Scintilla.vcxproj
index 1ceffb325..b8c6c4152 100644
--- a/win32/Scintilla.vcxproj
+++ b/win32/Scintilla.vcxproj
@@ -36,7 +36,7 @@
<PropertyGroup>
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
- <PlatformToolset>v142</PlatformToolset>
+ <PlatformToolset>v143</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
diff --git a/win32/deps.mak b/win32/deps.mak
index 652a4c012..98ddbaaf2 100644
--- a/win32/deps.mak
+++ b/win32/deps.mak
@@ -97,8 +97,21 @@ $(DIR_O)/CellBuffer.o: \
../src/Position.h \
../src/SplitVector.h \
../src/Partitioning.h \
+ ../src/RunStyles.h \
+ ../src/SparseVector.h \
+ ../src/ChangeHistory.h \
../src/CellBuffer.h \
../src/UniConversion.h
+$(DIR_O)/ChangeHistory.o: \
+ ../src/ChangeHistory.cxx \
+ ../include/ScintillaTypes.h \
+ ../src/Debugging.h \
+ ../src/Position.h \
+ ../src/SplitVector.h \
+ ../src/Partitioning.h \
+ ../src/RunStyles.h \
+ ../src/SparseVector.h \
+ ../src/ChangeHistory.h
$(DIR_O)/CharacterCategoryMap.o: \
../src/CharacterCategoryMap.cxx \
../src/CharacterCategoryMap.h
diff --git a/win32/makefile b/win32/makefile
index 5bb146056..5c17641ef 100644
--- a/win32/makefile
+++ b/win32/makefile
@@ -87,6 +87,7 @@ SRC_OBJS = \
$(DIR_O)/CaseConvert.o \
$(DIR_O)/CaseFolder.o \
$(DIR_O)/CellBuffer.o \
+ $(DIR_O)/ChangeHistory.o \
$(DIR_O)/CharacterCategoryMap.o \
$(DIR_O)/CharacterType.o \
$(DIR_O)/CharClassify.o \
diff --git a/win32/nmdeps.mak b/win32/nmdeps.mak
index ee7db779b..1682eb54a 100644
--- a/win32/nmdeps.mak
+++ b/win32/nmdeps.mak
@@ -97,8 +97,21 @@ $(DIR_O)/CellBuffer.obj: \
../src/Position.h \
../src/SplitVector.h \
../src/Partitioning.h \
+ ../src/RunStyles.h \
+ ../src/SparseVector.h \
+ ../src/ChangeHistory.h \
../src/CellBuffer.h \
../src/UniConversion.h
+$(DIR_O)/ChangeHistory.obj: \
+ ../src/ChangeHistory.cxx \
+ ../include/ScintillaTypes.h \
+ ../src/Debugging.h \
+ ../src/Position.h \
+ ../src/SplitVector.h \
+ ../src/Partitioning.h \
+ ../src/RunStyles.h \
+ ../src/SparseVector.h \
+ ../src/ChangeHistory.h
$(DIR_O)/CharacterCategoryMap.obj: \
../src/CharacterCategoryMap.cxx \
../src/CharacterCategoryMap.h
diff --git a/win32/scintilla.mak b/win32/scintilla.mak
index 3e089c46a..62b90385e 100644
--- a/win32/scintilla.mak
+++ b/win32/scintilla.mak
@@ -82,6 +82,7 @@ SRC_OBJS=\
$(DIR_O)\CaseConvert.obj \
$(DIR_O)\CaseFolder.obj \
$(DIR_O)\CellBuffer.obj \
+ $(DIR_O)\ChangeHistory.obj \
$(DIR_O)\CharacterCategoryMap.obj \
$(DIR_O)\CharacterType.obj \
$(DIR_O)\CharClassify.obj \