Skip to content

Commit af4c7c5

Browse files
authored
Provide suspendable undo manager and test (#914)
1 parent 4f71f2e commit af4c7c5

3 files changed

Lines changed: 78 additions & 0 deletions

File tree

richtextfx/src/integrationTest/java/org/fxmisc/richtext/api/UndoManagerTests.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import org.fxmisc.richtext.RichTextFXTestBase;
1010
import org.fxmisc.richtext.StyledTextArea;
1111
import org.fxmisc.richtext.model.SimpleEditableStyledDocument;
12+
import org.fxmisc.richtext.model.RichTextChange;
1213
import org.fxmisc.richtext.model.TextChange;
1314
import org.fxmisc.richtext.util.UndoUtils;
1415
import org.junit.Test;
1516
import org.junit.runner.RunWith;
17+
import org.reactfx.SuspendableYes;
1618

1719
import static org.junit.Assert.assertEquals;
1820
import static org.junit.Assert.assertFalse;
@@ -174,6 +176,20 @@ public void testForBug904() {
174176
interact( area::undo ); // should not throw Unexpected change received exception
175177
}
176178

179+
@Test
180+
public void suspendable_UndoManager_skips_style_check() {
181+
182+
SuspendableYes suspendUndo = new SuspendableYes();
183+
area.setUndoManager( UndoUtils.richTextSuspendableUndoManager( area, suspendUndo ) );
184+
write( "some text\n" );
185+
interact( () -> suspendUndo.suspendWhile( () -> area.setStyle( 5, 9, "-fx-font-weight: bold;" ) ) );
186+
write( "new line" );
187+
interact( area::undo ); // should not throw Unexpected change received exception
188+
189+
area.setUndoManager( UndoUtils.defaultUndoManager( area ) );
190+
RichTextChange.skipStyleComparison( false );
191+
}
192+
177193
}
178194

179195
public class UsingStyledTextArea extends RichTextFXTestBase {

richtextfx/src/main/java/org/fxmisc/richtext/model/RichTextChange.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,31 @@ public final PlainTextChange toPlainTextChange() {
4545
public final boolean isPlainTextIdentity() {
4646
return removed.getText().equals(inserted.getText());
4747
}
48+
49+
private static boolean skipStyleComparison = false;
50+
51+
public static void skipStyleComparison( boolean value )
52+
{
53+
skipStyleComparison = value;
54+
}
55+
56+
/*
57+
* This gets used, by the default UndoManagers supplied by UndoUtils,
58+
* to check that a submitted undo/redo matches the change reported.
59+
*/
60+
public boolean equals( Object other )
61+
{
62+
if( skipStyleComparison && other instanceof RichTextChange )
63+
{
64+
PlainTextChange otherChange = ((RichTextChange) other).toPlainTextChange();
65+
boolean matches = toPlainTextChange().equals( otherChange );
66+
if ( ! matches ) System.err.println(
67+
"Plain text comparison mismatch caused by text change"
68+
+" during undo manager suspension (styling ignored)."
69+
);
70+
return matches;
71+
}
72+
73+
return super.equals( other );
74+
}
4875
}

richtextfx/src/main/java/org/fxmisc/richtext/util/UndoUtils.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import org.fxmisc.richtext.model.TextChange;
88
import org.fxmisc.undo.UndoManager;
99
import org.fxmisc.undo.UndoManagerFactory;
10+
import org.fxmisc.undo.impl.MultiChangeUndoManagerImpl;
11+
import org.fxmisc.undo.impl.UnlimitedChangeQueue;
12+
import org.reactfx.SuspendableYes;
1013
import org.reactfx.value.Val;
1114

1215
import javafx.beans.value.ObservableBooleanValue;
@@ -121,6 +124,38 @@ public static <PS, SEG, S> UndoManager<List<RichTextChange<PS, SEG, S>>> richTex
121124
preventMergeDelay);
122125
};
123126

127+
/**
128+
* Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. New changes
129+
* emitted from the stream will not be merged with the previous change after {@link #DEFAULT_PREVENT_MERGE_DELAY}
130+
* <p><b>Note</b>: that <u>only styling changes</u> may occur <u>during suspension</u> of the undo manager.
131+
*/
132+
public static <PS, SEG, S> UndoManager<List<RichTextChange<PS, SEG, S>>> richTextSuspendableUndoManager(
133+
GenericStyledArea<PS, SEG, S> area, SuspendableYes suspendUndo) {
134+
return richTextSuspendableUndoManager(area, DEFAULT_PREVENT_MERGE_DELAY, suspendUndo);
135+
}
136+
137+
/**
138+
* Returns an UndoManager with an unlimited history that can undo/redo {@link RichTextChange}s. New changes
139+
* emitted from the stream will not be merged with the previous change after {@code preventMergeDelay}.
140+
* <p><b>Note</b>: that <u>only styling changes</u> may occur <u>during suspension</u> of the undo manager.
141+
*/
142+
public static <PS, SEG, S> UndoManager<List<RichTextChange<PS, SEG, S>>> richTextSuspendableUndoManager(
143+
GenericStyledArea<PS, SEG, S> area, Duration preventMergeDelay, SuspendableYes suspendUndo) {
144+
145+
RichTextChange.skipStyleComparison( true );
146+
147+
return new MultiChangeUndoManagerImpl<>
148+
(
149+
new UnlimitedChangeQueue<>(),
150+
TextChange::invert,
151+
applyMultiRichTextChange(area),
152+
TextChange::mergeWith,
153+
TextChange::isIdentity,
154+
area.multiRichChanges().conditionOn(suspendUndo),
155+
preventMergeDelay
156+
);
157+
};
158+
124159
/**
125160
* Returns an UndoManager with an unlimited history that can undo/redo {@link PlainTextChange}s. New changes
126161
* emitted from the stream will not be merged with the previous change

0 commit comments

Comments
 (0)