This Bugzilla instance is a read-only archive of historic NetBeans bug reports. To report a bug in NetBeans please follow the project's instructions for reporting issues.

View | Details | Raw Unified | Return to bug 199568
Collapse All | Expand All

(-)a/openide.text/apichanges.xml (+17 lines)
Lines 49-54 Link Here
49
<apidef name="text">Text API</apidef>
49
<apidef name="text">Text API</apidef>
50
</apidefs>
50
</apidefs>
51
<changes>
51
<changes>
52
    <change id="mark-commit-group">
53
        <api name="text"/>
54
        <summary>fix hole in commit-groups about empty commit group</summary>
55
        <version major="6" minor="40"/>
56
        <date day="20" month="6" year="2011"/>
57
        <author login="err"/>
58
        <compatibility addition="yes"/>
59
        <description>
60
            <p>
61
                Define semantics of a nested empty commit group.
62
                Add <code>MARK_COMMIT_GROUP</code> to fill a gap in API;
63
                It adds an inprogress commit-group and starts a new one.
64
            </p>
65
        </description>
66
        <class package="org.openide.text" name="CloneableEditorSupport"/>
67
        <issue number="199568"/>
68
    </change>
52
    <change id="CE.closeLast.boolean">
69
    <change id="CE.closeLast.boolean">
53
        <api name="text"/>
70
        <api name="text"/>
54
        <summary>CloneableEditor.closeLast</summary>
71
        <summary>CloneableEditor.closeLast</summary>
(-)a/openide.text/manifest.mf (-1 / +1 lines)
Lines 1-7 Link Here
1
Manifest-Version: 1.0
1
Manifest-Version: 1.0
2
OpenIDE-Module: org.openide.text
2
OpenIDE-Module: org.openide.text
3
OpenIDE-Module-Install: org/netbeans/modules/openide/text/Installer.class
3
OpenIDE-Module-Install: org/netbeans/modules/openide/text/Installer.class
4
OpenIDE-Module-Specification-Version: 6.39
4
OpenIDE-Module-Specification-Version: 6.40
5
OpenIDE-Module-Localizing-Bundle: org/openide/text/Bundle.properties
5
OpenIDE-Module-Localizing-Bundle: org/openide/text/Bundle.properties
6
AutoUpdate-Essential-Module: true
6
AutoUpdate-Essential-Module: true
7
7
(-)a/openide.text/src/org/openide/text/CloneableEditorSupport.java (-12 / +49 lines)
Lines 128-133 Link Here
128
* This class supports collecting multiple edits into a group which is treated
128
* This class supports collecting multiple edits into a group which is treated
129
* as a single edit by undo/redo. Send {@link #BEGIN_COMMIT_GROUP} and
129
* as a single edit by undo/redo. Send {@link #BEGIN_COMMIT_GROUP} and
130
* {@link #END_COMMIT_GROUP} to UndoableEditListener. These must always be paired.
130
* {@link #END_COMMIT_GROUP} to UndoableEditListener. These must always be paired.
131
* Send {@link #MARK_COMMIT_GROUP} to commit accumulated edits and to continue
132
* accumulating.
131
*
133
*
132
* @author Jaroslav Tulach
134
* @author Jaroslav Tulach
133
*/
135
*/
Lines 140-146 Link Here
140
     * Start a group of edits which will be committed as a single edit
142
     * Start a group of edits which will be committed as a single edit
141
     * for purpose of undo/redo.
143
     * for purpose of undo/redo.
142
     * Nesting semantics are that any BEGIN_COMMIT_GROUP and
144
     * Nesting semantics are that any BEGIN_COMMIT_GROUP and
143
     * END_COMMIT_GROUP delimits a commit-group.
145
     * END_COMMIT_GROUP delimits a commit-group, unless the group is
146
     * empty in which case the begin/end is ignored.
144
     * While coalescing edits, any undo/redo/save implicitly delimits
147
     * While coalescing edits, any undo/redo/save implicitly delimits
145
     * a commit-group.
148
     * a commit-group.
146
     * @since 6.34
149
     * @since 6.34
Lines 150-155 Link Here
150
     * @since 6.34
153
     * @since 6.34
151
     */
154
     */
152
    public static final UndoableEdit END_COMMIT_GROUP = UndoGroupManager.END_COMMIT_GROUP;
155
    public static final UndoableEdit END_COMMIT_GROUP = UndoGroupManager.END_COMMIT_GROUP;
156
    /**
157
     * Any coalesced edits become a commit-group and a new commit-group
158
     * is started.
159
     * @since 6.40
160
     */
161
    public static final UndoableEdit MARK_COMMIT_GROUP = UndoGroupManager.MARK_COMMIT_GROUP;
153
    private static final String PROP_PANE = "CloneableEditorSupport.Pane"; //NOI18N
162
    private static final String PROP_PANE = "CloneableEditorSupport.Pane"; //NOI18N
154
    private static final int DOCUMENT_NO = 0;
163
    private static final int DOCUMENT_NO = 0;
155
    private static final int DOCUMENT_LOADING = 1;
164
    private static final int DOCUMENT_LOADING = 1;
Lines 3474-3486 Link Here
3474
     * {@link CompoundEdit}.
3483
     * {@link CompoundEdit}.
3475
     * Thus <tt>undo()</tt> and <tt>redo()</tt> treat them 
3484
     * Thus <tt>undo()</tt> and <tt>redo()</tt> treat them 
3476
     * as a single undo/redo.</li>
3485
     * as a single undo/redo.</li>
3477
     * <li> Use {@link #commitUndoGroup} to commit accumulated
3486
     * <li>BEGIN/END nest.</li>
3487
     * <li> Issue MARK_COMMIT_GROUP to commit accumulated
3478
     * <tt>UndoableEdit</tt>s into a single <tt>CompoundEdit</tt>
3488
     * <tt>UndoableEdit</tt>s into a single <tt>CompoundEdit</tt>
3479
     * (and to continue accumulating);
3489
     * and to continue accumulating;
3480
     * an application could do this at strategic points, such as EndOfLine
3490
     * an application could do this at strategic points, such as EndOfLine
3481
     * input or cursor movement. In this way, the application can accumulate
3491
     * input or cursor movement.</li>
3482
     * large chunks.</li>
3483
     * <li>BEGIN/END nest.</li>
3484
     * </ol>
3492
     * </ol>
3485
     * @see UndoManager
3493
     * @see UndoManager
3486
     */
3494
     */
Lines 3489-3506 Link Here
3489
        private int buildUndoGroup;
3497
        private int buildUndoGroup;
3490
        /** accumulate edits here in undoGroup */
3498
        /** accumulate edits here in undoGroup */
3491
        private CompoundEdit undoGroup;
3499
        private CompoundEdit undoGroup;
3500
        /**
3501
         * Signal that nested group started and that current undo group
3502
         * must be committed if edit is added. Then can avoid doing the commit
3503
         * if the nested group turns out to be empty.
3504
         */
3505
        private int needsNestingCommit;
3492
3506
3493
        /**
3507
        /**
3494
         * Start a group of edits which will be committed as a single edit
3508
         * Start a group of edits which will be committed as a single edit
3495
         * for purpose of undo/redo.
3509
         * for purpose of undo/redo.
3496
         * Nesting semantics are that any BEGIN_COMMIT_GROUP and
3510
         * Nesting semantics are that any BEGIN_COMMIT_GROUP and
3497
         * END_COMMIT_GROUP delimits a commit-group.
3511
         * END_COMMIT_GROUP delimits a commit-group, unless the group is
3512
         * empty in which case the begin/end is ignored.
3498
         * While coalescing edits, any undo/redo/save implicitly delimits
3513
         * While coalescing edits, any undo/redo/save implicitly delimits
3499
         * a commit-group.
3514
         * a commit-group.
3500
         */
3515
         */
3501
        static final UndoableEdit BEGIN_COMMIT_GROUP = new CommitGroupEdit();
3516
        static final UndoableEdit BEGIN_COMMIT_GROUP = new CommitGroupEdit();
3502
        /** End a group of edits. */
3517
        /** End a group of edits. */
3503
        static final UndoableEdit END_COMMIT_GROUP = new CommitGroupEdit();
3518
        static final UndoableEdit END_COMMIT_GROUP = new CommitGroupEdit();
3519
        /**
3520
         * Any coalesced edits become a commit-group and a new commit-group
3521
         * is started.
3522
         */
3523
        static final UndoableEdit MARK_COMMIT_GROUP = new CommitGroupEdit();
3504
3524
3505
        /** SeparateEdit tags an UndoableEdit so the
3525
        /** SeparateEdit tags an UndoableEdit so the
3506
         * UndoGroupManager does not coalesce it.
3526
         * UndoGroupManager does not coalesce it.
Lines 3522-3527 Link Here
3522
                beginUndoGroup();
3542
                beginUndoGroup();
3523
            } else if(ue.getEdit() == END_COMMIT_GROUP) {
3543
            } else if(ue.getEdit() == END_COMMIT_GROUP) {
3524
                endUndoGroup();
3544
                endUndoGroup();
3545
            } else if(ue.getEdit() == MARK_COMMIT_GROUP) {
3546
                commitUndoGroup();
3525
            } else {
3547
            } else {
3526
                super.undoableEditHappened(ue);
3548
                super.undoableEditHappened(ue);
3527
            }
3549
            }
Lines 3531-3543 Link Here
3531
         * Direct this <tt>UndoGroupManager</tt> to begin coalescing any
3553
         * Direct this <tt>UndoGroupManager</tt> to begin coalescing any
3532
         * <tt>UndoableEdit</tt>s that are added into a <tt>CompoundEdit</tt>.
3554
         * <tt>UndoableEdit</tt>s that are added into a <tt>CompoundEdit</tt>.
3533
         * <p>If edits are already being coalesced and some have been 
3555
         * <p>If edits are already being coalesced and some have been 
3534
         * accumulated, they are commited as an atomic group and a new
3556
         * accumulated, they are flagged for commitment as an atomic group and
3535
         * group is started.
3557
         * a new group will be started.
3536
         * @see #addEdit
3558
         * @see #addEdit
3537
         * @see #endUndoGroup
3559
         * @see #endUndoGroup
3538
         */
3560
         */
3539
        private synchronized void beginUndoGroup() {
3561
        private synchronized void beginUndoGroup() {
3540
            commitUndoGroup();
3562
            if(undoGroup != null)
3563
                needsNestingCommit++;
3541
            ERR.log(Level.FINE, "beginUndoGroup: nesting {0}", buildUndoGroup);
3564
            ERR.log(Level.FINE, "beginUndoGroup: nesting {0}", buildUndoGroup);
3542
            buildUndoGroup++;
3565
            buildUndoGroup++;
3543
        }
3566
        }
Lines 3555-3564 Link Here
3555
            ERR.log(Level.FINE, "endUndoGroup: nesting {0}", buildUndoGroup);
3578
            ERR.log(Level.FINE, "endUndoGroup: nesting {0}", buildUndoGroup);
3556
            if(buildUndoGroup < 0) {
3579
            if(buildUndoGroup < 0) {
3557
                ERR.log(Level.INFO, null, new Exception("endUndoGroup without beginUndoGroup"));
3580
                ERR.log(Level.INFO, null, new Exception("endUndoGroup without beginUndoGroup"));
3581
                // slam buildUndoGroup to 0 to disable nesting
3558
                buildUndoGroup = 0;
3582
                buildUndoGroup = 0;
3559
            }
3583
            }
3560
            // slam buildUndoGroup to 0 to disable nesting
3584
            if(needsNestingCommit <= 0)
3561
            commitUndoGroup();
3585
                commitUndoGroup();
3586
            if(--needsNestingCommit < 0)
3587
                needsNestingCommit = 0;
3562
        }
3588
        }
3563
3589
3564
        /**
3590
        /**
Lines 3575-3580 Link Here
3575
            if(undoGroup == null) {
3601
            if(undoGroup == null) {
3576
                return;
3602
                return;
3577
            }
3603
            }
3604
3605
            // undoGroup is being set to null,
3606
            // needsNestingCommit has no meaning now
3607
            needsNestingCommit = 0;
3608
3578
            // super.addEdit may end up in this.addEdit,
3609
            // super.addEdit may end up in this.addEdit,
3579
            // so buildUndoGroup must be false
3610
            // so buildUndoGroup must be false
3580
            int saveBuildUndoGroup = buildUndoGroup;
3611
            int saveBuildUndoGroup = buildUndoGroup;
Lines 3602-3607 Link Here
3602
        }
3633
        }
3603
3634
3604
        /**
3635
        /**
3636
         * If there's a pending undo group that needs to be committed
3637
         * then commit it.
3605
         * If this <tt>UndoManager</tt> is coalescing edits then add
3638
         * If this <tt>UndoManager</tt> is coalescing edits then add
3606
         * <tt>anEdit</tt> to the accumulating <tt>CompoundEdit</tt>.
3639
         * <tt>anEdit</tt> to the accumulating <tt>CompoundEdit</tt>.
3607
         * Otherwise, add it to this UndoManager. In either case the
3640
         * Otherwise, add it to this UndoManager. In either case the
Lines 3615-3620 Link Here
3615
            if(!isInProgress())
3648
            if(!isInProgress())
3616
                return false;
3649
                return false;
3617
3650
3651
            if(needsNestingCommit > 0) {
3652
                commitUndoGroup();
3653
            }
3654
3618
            if(buildUndoGroup > 0) {
3655
            if(buildUndoGroup > 0) {
3619
                if(anEdit instanceof SeparateEdit)
3656
                if(anEdit instanceof SeparateEdit)
3620
                    return commitAddEdit(anEdit);
3657
                    return commitAddEdit(anEdit);
(-)a/openide.text/test/unit/src/org/openide/text/UndoRedoWrappingCooperationTest.java (-27 / +185 lines)
Lines 47-52 Link Here
47
import java.beans.PropertyChangeListener;
47
import java.beans.PropertyChangeListener;
48
import java.io.IOException;
48
import java.io.IOException;
49
import java.util.ArrayList;
49
import java.util.ArrayList;
50
import java.util.logging.Level;
51
import java.util.logging.Logger;
50
import javax.swing.event.UndoableEditEvent;
52
import javax.swing.event.UndoableEditEvent;
51
import javax.swing.event.UndoableEditListener;
53
import javax.swing.event.UndoableEditListener;
52
import javax.swing.text.*;
54
import javax.swing.text.*;
Lines 90-107 Link Here
90
        return new NbLikeEditorKit();
92
        return new NbLikeEditorKit();
91
    }
93
    }
92
94
95
    // could install a logger "Handler" and test the warning only when
96
    // expected. Maybe later.
97
    Level disableWarning()
98
    {
99
        Logger l = Logger.getLogger("org.openide.text.CloneableEditorSupport");
100
        Level level = l.getLevel();
101
        l.setLevel(Level.SEVERE);
102
        return level;
103
    }
104
    void enableWarning(Level level)
105
    {
106
        Logger l = Logger.getLogger("org.openide.text.CloneableEditorSupport");
107
        l.setLevel(level);
108
    }
109
93
    // Use these methods with the UndoRedoGroup patch
110
    // Use these methods with the UndoRedoGroup patch
94
    CompoundEdit beginChunk(Document d) {
111
    CompoundEdit beginChunk(Document d) {
95
        // ur().beginUndoGroup();
96
        sendUndoableEdit(d, CloneableEditorSupport.BEGIN_COMMIT_GROUP);
112
        sendUndoableEdit(d, CloneableEditorSupport.BEGIN_COMMIT_GROUP);
97
        return null;
113
        return null;
98
    }
114
    }
99
    
115
    
116
    void endChunk(Document d) {
117
        endChunk(d, null);
118
    }
119
100
    void endChunk(Document d, CompoundEdit ce) {
120
    void endChunk(Document d, CompoundEdit ce) {
101
        // ur().endUndoGroup();
102
        sendUndoableEdit(d, CloneableEditorSupport.END_COMMIT_GROUP);
121
        sendUndoableEdit(d, CloneableEditorSupport.END_COMMIT_GROUP);
103
    }
122
    }
104
123
124
    void markChunk(Document d) {
125
        sendUndoableEdit(d, CloneableEditorSupport.MARK_COMMIT_GROUP);
126
    }
127
105
    void sendUndoableEdit(Document d, UndoableEdit ue) {
128
    void sendUndoableEdit(Document d, UndoableEdit ue) {
106
        if(d instanceof AbstractDocument) {
129
        if(d instanceof AbstractDocument) {
107
            UndoableEditListener[] uels = ((AbstractDocument)d).getUndoableEditListeners();
130
            UndoableEditListener[] uels = ((AbstractDocument)d).getUndoableEditListeners();
Lines 203-235 Link Here
203
        endChunk(d, ce);
226
        endChunk(d, ce);
204
        assertEquals("chunk: data", "ab", d.getText(0, d.getLength()));
227
        assertEquals("chunk: data", "ab", d.getText(0, d.getLength()));
205
228
206
        endChunk(d, ce);
229
        Level level = disableWarning();
207
        endChunk(d, ce);
230
        try {
231
            endChunk(d, ce);
232
            endChunk(d, ce);
208
233
209
        assertEquals("extraEnd: data", "ab", d.getText(0, d.getLength()));
234
            assertEquals("extraEnd: data", "ab", d.getText(0, d.getLength()));
210
        assertTrue("extraEnd: modified", support.isModified());
235
            assertTrue("extraEnd: modified", support.isModified());
211
        assertTrue("extraEnd: can undo", ur().canUndo());
236
            assertTrue("extraEnd: can undo", ur().canUndo());
212
        assertFalse("extraEnd: no redo", ur().canRedo());
237
            assertFalse("extraEnd: no redo", ur().canRedo());
213
238
214
        d.insertString(d.getLength(), "c", null);
239
            d.insertString(d.getLength(), "c", null);
215
        d.insertString(d.getLength(), "d", null);
240
            d.insertString(d.getLength(), "d", null);
216
        endChunk(d, ce);
241
            endChunk(d, ce);
217
        assertEquals("extraEnd2: data", "abcd", d.getText(0, d.getLength()));
242
            assertEquals("extraEnd2: data", "abcd", d.getText(0, d.getLength()));
218
        ur().undo();
243
            ur().undo();
219
        endChunk(d, ce);
244
            endChunk(d, ce);
220
        assertEquals("undo1: data", "abc", d.getText(0, d.getLength()));
245
            assertEquals("undo1: data", "abc", d.getText(0, d.getLength()));
221
        ur().undo();
246
            ur().undo();
222
        assertEquals("undo2: data", "ab", d.getText(0, d.getLength()));
247
            assertEquals("undo2: data", "ab", d.getText(0, d.getLength()));
223
        ur().undo();
248
            ur().undo();
224
        endChunk(d, ce);
249
            endChunk(d, ce);
225
        assertEquals("undo3: data", "", d.getText(0, d.getLength()));
250
            assertEquals("undo3: data", "", d.getText(0, d.getLength()));
226
        ur().redo();
251
            ur().redo();
227
        assertEquals("redo1: data", "ab", d.getText(0, d.getLength()));
252
            assertEquals("redo1: data", "ab", d.getText(0, d.getLength()));
228
        ur().redo();
253
            ur().redo();
229
        endChunk(d, ce);
254
            endChunk(d, ce);
230
        assertEquals("redo2: data", "abc", d.getText(0, d.getLength()));
255
            assertEquals("redo2: data", "abc", d.getText(0, d.getLength()));
231
        ur().redo();
256
            ur().redo();
232
        assertEquals("redo3: data", "abcd", d.getText(0, d.getLength()));
257
            assertEquals("redo3: data", "abcd", d.getText(0, d.getLength()));
258
        } finally {
259
            enableWarning(level);
260
        }
233
    }
261
    }
234
262
235
    public void testUndoRedoWhileActiveChunk() throws Exception {
263
    public void testUndoRedoWhileActiveChunk() throws Exception {
Lines 369-374 Link Here
369
        ur().undo();
397
        ur().undo();
370
        assertEquals("undo3", "", d.getText(0, d.getLength()));
398
        assertEquals("undo3", "", d.getText(0, d.getLength()));
371
    }
399
    }
400
401
    public void testNestedEmpyChunks() throws Exception {
402
        content = "";
403
        StyledDocument d = support.openDocument();
404
        beginChunk(d);
405
        d.insertString(d.getLength(), "a", null);
406
        d.insertString(d.getLength(), "b", null);
407
408
        // should have no effect
409
        beginChunk(d);
410
        endChunk(d);
411
412
        d.insertString(d.getLength(), "e", null);
413
        d.insertString(d.getLength(), "f", null);
414
415
        endChunk(d);
416
417
        assertEquals("data", "abef", d.getText(0, d.getLength()));
418
419
        ur().undo();
420
        assertEquals("undo3", "", d.getText(0, d.getLength()));
421
    }
422
423
    public void testNestedEmpyChunks2() throws Exception {
424
        content = "";
425
        StyledDocument d = support.openDocument();
426
        beginChunk(d);
427
        d.insertString(d.getLength(), "a", null);
428
        d.insertString(d.getLength(), "b", null);
429
430
        // should have no effect
431
        beginChunk(d);
432
        beginChunk(d);
433
        endChunk(d);
434
        endChunk(d);
435
        beginChunk(d);
436
        endChunk(d);
437
438
        d.insertString(d.getLength(), "e", null);
439
        d.insertString(d.getLength(), "f", null);
440
441
        endChunk(d);
442
443
        assertEquals("data", "abef", d.getText(0, d.getLength()));
444
445
        ur().undo();
446
        assertEquals("undo3", "", d.getText(0, d.getLength()));
447
    }
448
449
    public void testNestedEmpyChunks3() throws Exception {
450
        content = "";
451
        StyledDocument d = support.openDocument();
452
        beginChunk(d);
453
        d.insertString(d.getLength(), "a", null);
454
        d.insertString(d.getLength(), "b", null);
455
456
        beginChunk(d);
457
        d.insertString(d.getLength(), "c", null);
458
459
        // should have no effect
460
        beginChunk(d);
461
        endChunk(d);
462
463
        d.insertString(d.getLength(), "d", null);
464
        endChunk(d);
465
466
        // should have no effect
467
        beginChunk(d);
468
        endChunk(d);
469
470
        d.insertString(d.getLength(), "e", null);
471
472
        // should have no effect
473
        beginChunk(d);
474
        endChunk(d);
475
476
        d.insertString(d.getLength(), "f", null);
477
478
        // should have no effect
479
        beginChunk(d);
480
        endChunk(d);
481
482
        d.insertString(d.getLength(), "g", null);
483
484
        endChunk(d);
485
486
        assertEquals("data", "abcdefg", d.getText(0, d.getLength()));
487
488
        // following fails if nesting not supported
489
        ur().undo();
490
        assertEquals("undo1", "abcd", d.getText(0, d.getLength()));
491
492
        ur().undo();
493
        assertEquals("undo2", "ab", d.getText(0, d.getLength()));
494
495
        ur().undo();
496
        assertEquals("undo3", "", d.getText(0, d.getLength()));
497
    }
498
499
    public void testMarkCommitGroup() throws Exception {
500
        content = "";
501
        StyledDocument d = support.openDocument();
502
        beginChunk(d);
503
        d.insertString(d.getLength(), "a", null);
504
        d.insertString(d.getLength(), "b", null);
505
506
        markChunk(d); // creates a separate undoable chunk
507
508
        d.insertString(d.getLength(), "c", null);
509
        d.insertString(d.getLength(), "d", null);
510
511
        markChunk(d);
512
513
        d.insertString(d.getLength(), "e", null);
514
        d.insertString(d.getLength(), "f", null);
515
516
        endChunk(d);
517
518
        assertEquals("data", "abcdef", d.getText(0, d.getLength()));
519
520
        // following fails if nesting not supported
521
        ur().undo();
522
        assertEquals("undo1", "abcd", d.getText(0, d.getLength()));
523
524
        ur().undo();
525
        assertEquals("undo2", "ab", d.getText(0, d.getLength()));
526
527
        ur().undo();
528
        assertEquals("undo3", "", d.getText(0, d.getLength()));
529
    }
372
    
530
    
373
    //
531
    //
374
    // Implementation of the CloneableEditorSupport.Env
532
    // Implementation of the CloneableEditorSupport.Env

Return to bug 199568