--- a/editor.document/apichanges.xml +++ a/editor.document/apichanges.xml @@ -112,6 +112,22 @@ + + + CustomUndoDocument added + + + + + +

+ CustomUndoDocument allows to add extra undoable edits during atomic transactions + over a document. +

+
+ + +
Added ShiftPositions --- a/editor.document/manifest.mf +++ a/editor.document/manifest.mf @@ -1,4 +1,4 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.editor.document OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/document/Bundle.properties -OpenIDE-Module-Implementation-Version: 1 +OpenIDE-Module-Implementation-Version: 2 --- a/editor.document/nbproject/project.properties +++ a/editor.document/nbproject/project.properties @@ -1,6 +1,6 @@ javac.source=1.7 javac.compilerargs=-Xlint -Xlint:-serial -spec.version.base=1.7.0 +spec.version.base=1.8.0 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml is.autoload=true --- a/editor.document/src/org/netbeans/api/editor/document/CustomUndoDocument.java +++ a/editor.document/src/org/netbeans/api/editor/document/CustomUndoDocument.java @@ -0,0 +1,76 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2016 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2016 Sun Microsystems, Inc. + */ +package org.netbeans.api.editor.document; + +import javax.swing.undo.UndoableEdit; + +/** + * Document that allows adding of a custom undoable edit during atomic transaction. + *
+ * To obtain CustomUndoDocument instance the {@link LineDocumentUtils#as(javax.swing.text.Document, java.lang.Class) } + * or {@link LineDocumentUtils#asRequired(javax.swing.text.Document, java.lang.Class) } may be used: + * + *
+ *   Document doc = ...
+ *   CustomUndoDocument customUndoDoc = LineDocumentUtils.asRequired(doc, CustomUndoDocument.class);
+ * 
+ *
+ * + * @author Miloslav Metelka + * @since 1.8 + */ +public interface CustomUndoDocument { + + /** + * Add a custom undoable edit to the undoable edits being created + * during an atomic transaction over the document. + *
+ * For example editor caret may add an undo edit allowing to restore caret(s) positions + * before (or after) modifications during the atomic lock. + * + * @param edit non-null undoable edit. + * @throws IllegalStateException if the document is not under atomic lock. + * @since 1.8 + */ + public void addUndoableEdit(UndoableEdit edit); + +} --- a/editor.lib/src/org/netbeans/editor/BaseDocument.java +++ a/editor.lib/src/org/netbeans/editor/BaseDocument.java @@ -86,6 +86,7 @@ import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.UndoableEdit; +import org.netbeans.api.editor.document.CustomUndoDocument; import org.netbeans.api.editor.document.LineDocument; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.editor.mimelookup.MimePath; @@ -130,7 +131,7 @@ */ @SuppressWarnings("ClassWithMultipleLoggers") -public class BaseDocument extends AbstractDocument implements AtomicLockDocument, LineDocument { +public class BaseDocument extends AbstractDocument implements AtomicLockDocument, LineDocument, CustomUndoDocument { static { EditorPackageAccessor.register(new Accessor()); --- a/editor.lib/src/org/netbeans/editor/BaseKit.java +++ a/editor.lib/src/org/netbeans/editor/BaseKit.java @@ -105,6 +105,7 @@ import org.netbeans.api.editor.caret.CaretInfo; import org.netbeans.api.editor.EditorActionRegistration; import org.netbeans.api.editor.EditorActionRegistrations; +import org.netbeans.api.editor.EditorUtilities; import org.netbeans.api.editor.caret.EditorCaret; import org.netbeans.api.editor.mimelookup.MimeLookup; import org.netbeans.api.editor.mimelookup.MimePath; @@ -1170,11 +1171,7 @@ boolean alreadyBeeped = false; DocumentUtilities.setTypingModification(doc, true); try { - // Store current state of caret(s) for undo - UndoableEdit caretUndoEdit = CaretUndo.createCaretUndoEdit(caret, doc, false); - if (caretUndoEdit != null) { - doc.addUndoableEdit(caretUndoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); for (CaretInfo c : carets) { if (c.isSelection()) { // valid selection int p0 = Math.min(c.getDot(), c.getMark()); @@ -1231,11 +1228,7 @@ } } } - // Store current state of caret(s) for redo - UndoableEdit caretRedoEdit = CaretUndo.createCaretUndoEdit(caret, doc, true); - if (caretRedoEdit != null) { - doc.addUndoableEdit(caretRedoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); } finally { DocumentUtilities.setTypingModification(doc, false); @@ -1262,11 +1255,7 @@ doc.runAtomicAsUser(new Runnable() { public void run() { boolean alreadyBeeped = false; - // Store current state of caret(s) for undo - UndoableEdit caretUndoEdit = CaretUndo.createCaretUndoEdit(caret, doc, false); - if (caretUndoEdit != null) { - doc.addUndoableEdit(caretUndoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); if (target.getCaret().isSelectionVisible() && caret.getDot() != caret.getMark()) { // valid selection EditorUI editorUI = Utilities.getEditorUI(target); Boolean overwriteMode = (Boolean) editorUI.getProperty(EditorUI.OVERWRITE_MODE_PROPERTY); @@ -1296,11 +1285,7 @@ target.getToolkit().beep(); } } - // Store current state of caret(s) for redo - UndoableEdit caretRedoEdit = CaretUndo.createCaretUndoEdit(caret, doc, true); - if (caretRedoEdit != null) { - doc.addUndoableEdit(caretRedoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); } }); @@ -2021,11 +2006,7 @@ public void run() { boolean alreadyBeeped = false; DocumentUtilities.setTypingModification(doc, true); - // Store current state of caret(s) for undo - UndoableEdit caretUndoEdit = CaretUndo.createCaretUndoEdit(caret, doc, false); - if (caretUndoEdit != null) { - doc.addUndoableEdit(caretUndoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); try { for (CaretInfo c : carets) { if (c.isSelection()) { @@ -2068,11 +2049,7 @@ } } } - // Store current state of caret(s) for redo - UndoableEdit caretRedoEdit = CaretUndo.createCaretUndoEdit(caret, doc, true); - if (caretRedoEdit != null) { - doc.addUndoableEdit(caretRedoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); } finally { DocumentUtilities.setTypingModification(doc, false); @@ -2093,11 +2070,7 @@ doc.runAtomicAsUser (new Runnable () { public void run () { DocumentUtilities.setTypingModification(doc, true); - // Store current state of caret(s) for undo - UndoableEdit caretUndoEdit = CaretUndo.createCaretUndoEdit(caret, doc, false); - if (caretUndoEdit != null) { - doc.addUndoableEdit(caretUndoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); try { List dotAndMarkPosPairs = new ArrayList<>(2); dotAndMarkPosPairs.add(doc.createPosition(caret.getDot())); @@ -2112,11 +2085,7 @@ } else { doc.remove(Math.min(dot, mark), Math.abs(dot - mark)); } - // Store current state of caret(s) for redo - UndoableEdit caretRedoEdit = CaretUndo.createCaretUndoEdit(caret, doc, true); - if (caretRedoEdit != null) { - doc.addUndoableEdit(caretRedoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); } catch (BadLocationException e) { target.getToolkit().beep(); } finally { @@ -2144,11 +2113,7 @@ doc.runAtomicAsUser (new Runnable () { public void run () { DocumentUtilities.setTypingModification(doc, true); - // Store current state of caret(s) for undo - UndoableEdit caretUndoEdit = CaretUndo.createCaretUndoEdit(caret, doc, false); - if (caretUndoEdit != null) { - doc.addUndoableEdit(caretUndoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); try { if (nextChar) { // remove next char doc.remove(dot, 1); @@ -2170,11 +2135,7 @@ } finally { DocumentUtilities.setTypingModification(doc, false); } - // Store current state of caret(s) for redo - UndoableEdit caretRedoEdit = CaretUndo.createCaretUndoEdit(caret, doc, true); - if (caretRedoEdit != null) { - doc.addUndoableEdit(caretRedoEdit); - } + EditorUtilities.addCaretUndoableEdit(doc, caret); } }); --- a/editor.lib2/apichanges.xml +++ a/editor.lib2/apichanges.xml @@ -107,6 +107,21 @@ + + EditorUtilities.addCaretUndoableEdit added + + + + + +

+ EditorUtilities.addCaretUndoableEdit allows actions to remember caret position + during an atomic transaction over a document. +

+
+ + +
Support for sticky windows in the editor --- a/editor.lib2/manifest.mf +++ a/editor.lib2/manifest.mf @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.editor.lib2/1 -OpenIDE-Module-Implementation-Version: 45 +OpenIDE-Module-Implementation-Version: 46 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/editor/lib2/Bundle.properties OpenIDE-Module-Layer: org/netbeans/modules/editor/lib2/resources/layer.xml OpenIDE-Module-Needs: org.netbeans.modules.editor.actions --- a/editor.lib2/nbproject/project.properties +++ a/editor.lib2/nbproject/project.properties @@ -43,7 +43,7 @@ is.autoload=true javac.source=1.7 javac.compilerargs=-Xlint:unchecked -spec.version.base=2.9.0 +spec.version.base=2.10.0 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml --- a/editor.lib2/src/org/netbeans/api/editor/EditorUtilities.java +++ a/editor.lib2/src/org/netbeans/api/editor/EditorUtilities.java @@ -45,7 +45,14 @@ package org.netbeans.api.editor; import javax.swing.Action; +import javax.swing.text.Caret; +import javax.swing.text.Document; import javax.swing.text.EditorKit; +import javax.swing.undo.UndoableEdit; +import org.netbeans.api.editor.document.AtomicLockDocument; +import org.netbeans.api.editor.document.CustomUndoDocument; +import org.netbeans.api.editor.document.LineDocumentUtils; +import org.netbeans.modules.editor.lib2.CaretUndo; import org.netbeans.modules.editor.lib2.actions.EditorActionUtilities; @@ -82,6 +89,39 @@ return EditorActionUtilities.getAction(editorKit, actionName); } + /** + * Add an undoable edit describing current state of caret(s) during document's atomic section. + *
+ * This method is typically called at the beginning of the atomic section over the document + * so that a subsequent undo would restore original caret offsets that were not yet modified + * by the actual changes performed during the atomic section. + *
+ * The method may also be called at the end of the atomic section + * in case the atomic section performed explicit caret movements. + *
+ * The created undoable edit will be added to document's compound undoable edit created for the atomic section. + * That edit will be fired by the document to an undo manager's listener upon completion of the atomic section. + * Therefore the document should adhere to {@link CustomUndoDocument} otherwise the method would do nothing. + * + * @param doc document to which the created undoable edit will be added. + * Null may be passed then the method has no effect. + * @param caret non-null caret which state should be stored + * @see CustomUndoDocument + * @see AtomicLockDocument + * @throws IllegalStateException if this method is called outside of an atomic section. + * @since 2.10 + */ + public static void addCaretUndoableEdit(Document doc, Caret caret) { + CustomUndoDocument customUndoDocument = LineDocumentUtils.as(doc, CustomUndoDocument.class); + if (customUndoDocument != null) { + UndoableEdit caretUndoEdit = CaretUndo.createCaretUndoEdit(caret, doc); + if (caretUndoEdit != null) { + customUndoDocument.addUndoableEdit(caretUndoEdit); + } // Might be null if caret is not installed in a text component and its document + } + } + + // /** // * Reset caret's magic position. // * @param component target text component. --- a/editor.lib2/src/org/netbeans/modules/editor/lib2/CaretUndo.java +++ a/editor.lib2/src/org/netbeans/modules/editor/lib2/CaretUndo.java @@ -61,17 +61,17 @@ /** * Create undoable edit that returns caret to its original state when the edit is undone. *
- * This edit is typically created both at the begining of an action that does some document modifications - * for purpose of undo and at its end for redo purpose. + * This edit is typically created both at the begining and end of an action that does some document modifications. * * @param caret non-null caret. * @param doc non-null document to which the undoable edit will be added. - * @param forRedo whether the edit is created for undo purpose (at action's begining) or for redo purpose - * (at action's end). * @return edit allowing to restore caret state upon undo call on the returned edit or null * if caret is not installed in a valid document. + *
+ * Future optimizations may return null edit also in case when there was no change in carets + * since the preceding call to this method inside the same atomic transaction over the document. */ - public static UndoableEdit createCaretUndoEdit(@NonNull Caret caret, @NonNull Document doc, boolean forRedo) { + public static UndoableEdit createCaretUndoEdit(@NonNull Caret caret, @NonNull Document doc) { UndoableEdit ret; if (caret instanceof EditorCaret) { EditorCaret eCaret = (EditorCaret) caret; @@ -92,12 +92,12 @@ if (caretsSize == 1) { // Single-caret case if (!complexPos) { // Regular positions if (dotOffset == markOffset) { // No selection - ret = new CaretUndoEdit(doc, forRedo, dotOffset); + ret = new CaretUndoEdit(doc, dotOffset); } else { // Selection - ret = new CaretUndoEdit.ComplexEdit(doc, forRedo, dotOffset, markOffset, null); + ret = new CaretUndoEdit.ComplexEdit(doc, dotOffset, markOffset, null); } } else { // Complex positions - ret = new CaretUndoEdit.ComplexEdit(doc, forRedo, dotOffset, -1, new int[] { + ret = new CaretUndoEdit.ComplexEdit(doc, dotOffset, -1, new int[] { dotSplitOffset, markOffset, markSplitOffset }); } @@ -155,7 +155,7 @@ offsets[i++] = ShiftPositions.getShift(dotPos); offsets[i++] = ShiftPositions.getShift(markPos); } - ret = new CaretUndoEdit.ComplexEdit(doc, forRedo, dotOffset, markOffset, offsets); + ret = new CaretUndoEdit.ComplexEdit(doc, dotOffset, markOffset, offsets); } } else { // dotPos == null => return null edit @@ -166,9 +166,9 @@ int dotOffset = caret.getDot(); int markOffset = caret.getMark(); if (markOffset != dotOffset) { - ret = new CaretUndoEdit.ComplexEdit(doc, forRedo, dotOffset, markOffset, null); + ret = new CaretUndoEdit.ComplexEdit(doc, dotOffset, markOffset, null); } else { - ret = new CaretUndoEdit(doc, forRedo, dotOffset); + ret = new CaretUndoEdit(doc, dotOffset); } } return ret; --- a/editor.lib2/src/org/netbeans/modules/editor/lib2/CaretUndoEdit.java +++ a/editor.lib2/src/org/netbeans/modules/editor/lib2/CaretUndoEdit.java @@ -63,8 +63,6 @@ */ class CaretUndoEdit extends AbstractUndoableEdit { - private static final int FOR_REDO_BIT = (1 << 31); - final Document doc; // (16=super)+4=20 bytes /** @@ -72,7 +70,7 @@ */ private int dotOffset; // 24 bytes - CaretUndoEdit(Document doc, boolean forRedo, int dotOffset) { + CaretUndoEdit(Document doc, int dotOffset) { if (doc == null) { throw new IllegalArgumentException("doc parameter must not be null"); // NOI18N } @@ -80,54 +78,42 @@ throw new IllegalStateException("Negative dotOffset=" + dotOffset + " not supported here"); // NOI18N } this.doc = doc; - this.dotOffset = forRedo ? (FOR_REDO_BIT | dotOffset) : dotOffset; + this.dotOffset = dotOffset; } @Override public void undo() throws CannotUndoException { super.undo(); - if (!isForRedo()) { - JTextComponent c = EditorRegistry.findComponent(doc); - if (c != null) { - Caret caret = c.getCaret(); - if (caret instanceof EditorCaret) { - try { - restoreEditorCaret((EditorCaret) caret); - } catch (BadLocationException ex) { - // Ignore caret restoration - } - } else { - restoreLegacyCaret(caret); - } - } - } + restoreCaret(); } @Override public void redo() throws CannotRedoException { super.redo(); - if (isForRedo()) { - JTextComponent c = EditorRegistry.findComponent(doc); - if (c != null) { - Caret caret = c.getCaret(); - if (caret instanceof EditorCaret) { - try { - restoreEditorCaret((EditorCaret) caret); - } catch (BadLocationException ex) { - // Ignore caret restoration - } - } else { - restoreLegacyCaret(caret); - } - } - } + restoreCaret(); } - + @Override public boolean isSignificant() { return super.isSignificant(); } + private void restoreCaret() { + JTextComponent c = EditorRegistry.findComponent(doc); + if (c != null) { + Caret caret = c.getCaret(); + if (caret instanceof EditorCaret) { + try { + restoreEditorCaret((EditorCaret) caret); + } catch (BadLocationException ex) { + // Ignore caret restoration + } + } else { + restoreLegacyCaret(caret); + } + } + } + protected void restoreEditorCaret(EditorCaret caret) throws BadLocationException { Position dotPos = doc.createPosition(getDotOffset()); caret.replaceCarets(Arrays.asList(dotPos, dotPos)); @@ -137,12 +123,8 @@ caret.setDot(getDotOffset()); } - boolean isForRedo() { - return (dotOffset & FOR_REDO_BIT) != 0; - } - int getDotOffset() { - return (dotOffset & ~FOR_REDO_BIT); + return dotOffset; } static final class ComplexEdit extends CaretUndoEdit { @@ -157,8 +139,8 @@ int[] extraDotAndMarkOffsets; // 32 bytes - ComplexEdit(Document doc, boolean forRedo, int dotOffset, int markOffset, int[] extraDotAndMarkOffsets) { - super(doc, forRedo, dotOffset); + ComplexEdit(Document doc, int dotOffset, int markOffset, int[] extraDotAndMarkOffsets) { + super(doc, dotOffset); this.markOffset = markOffset; this.extraDotAndMarkOffsets = extraDotAndMarkOffsets; } --- a/editor.lib2/test/unit/src/org/netbeans/api/editor/EditorUtilitiesTest.java +++ a/editor.lib2/test/unit/src/org/netbeans/api/editor/EditorUtilitiesTest.java @@ -43,10 +43,17 @@ package org.netbeans.api.editor; import javax.swing.Action; +import javax.swing.JEditorPane; +import javax.swing.SwingUtilities; import javax.swing.text.DefaultEditorKit; +import javax.swing.text.Document; import javax.swing.text.EditorKit; +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoableEdit; import org.junit.Test; import static org.junit.Assert.*; +import org.netbeans.api.editor.caret.EditorCaret; +import org.netbeans.api.editor.document.CustomUndoDocument; /** * @@ -71,4 +78,28 @@ fail("Action " + actionName + " not found."); } + @Test + public void testAddCaretUndoableEdit() throws Exception { + final JEditorPane pane = new JEditorPane("text/plain", "Haf"); + //final CompoundEdit compoundEdit = new CompoundEdit(); + final boolean[] editAdded = { false }; + final Document doc = pane.getDocument(); + doc.putProperty(CustomUndoDocument.class, new CustomUndoDocument() { + @Override + public void addUndoableEdit(UndoableEdit edit) { + editAdded[0] = true; + } + }); + final EditorCaret editorCaret = new EditorCaret(); + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + pane.setCaret(editorCaret); + EditorUtilities.addCaretUndoableEdit(doc, editorCaret); + } + }); + + assertTrue(editAdded[0]); + } + }