diff --git a/editor.lib/src/org/netbeans/editor/BaseDocument.java b/editor.lib/src/org/netbeans/editor/BaseDocument.java --- a/editor.lib/src/org/netbeans/editor/BaseDocument.java +++ b/editor.lib/src/org/netbeans/editor/BaseDocument.java @@ -109,6 +109,7 @@ import org.netbeans.modules.editor.lib2.document.EditorDocumentHandler; import org.netbeans.modules.editor.lib2.document.EditorDocumentServices; import org.netbeans.modules.editor.lib2.document.LineElementRoot; +import org.netbeans.modules.editor.lib2.document.ListUndoableEdit; import org.netbeans.modules.editor.lib2.document.ReadWriteBuffer; import org.netbeans.modules.editor.lib2.document.ReadWriteUtils; import org.netbeans.modules.editor.lib2.document.StableCompoundEdit; @@ -570,6 +571,9 @@ TrailingWhitespaceRemove.install(this); undoEditWrappers = MimeLookup.getLookup(mimeType).lookupAll(UndoableEditWrapper.class); + if (undoEditWrappers != null && undoEditWrappers.isEmpty()) { + undoEditWrappers = null; + } if (weakPrefsListener == null) { // the listening could have already been initialized from setMimeType(), which @@ -1579,13 +1583,21 @@ protected @Override void fireUndoableEditUpdate(UndoableEditEvent e) { // Possibly wrap contained edit if (undoEditWrappers != null) { - UndoableEdit origEdit = e.getEdit(); - UndoableEdit edit = origEdit; + UndoableEdit edit = e.getEdit(); + ListUndoableEdit listEdit = null; for (UndoableEditWrapper wrapper : undoEditWrappers) { - edit = wrapper.wrap(edit, this); + UndoableEdit wrapEdit = wrapper.wrap(edit, this); + if (wrapEdit != edit) { + if (listEdit == null) { + listEdit = new ListUndoableEdit(edit, wrapEdit); + } else { + listEdit.setDelegate(wrapEdit); + } + edit = wrapEdit; + } } - if (edit != origEdit) { - e = new UndoableEditEvent(this, edit); + if (listEdit != null) { + e = new UndoableEditEvent(this, listEdit); } } diff --git a/editor.lib/test/unit/src/org/netbeans/editor/TestingUndoableEditWrapper2.java b/editor.lib/test/unit/src/org/netbeans/editor/TestingUndoableEditWrapper2.java new file mode 100644 --- /dev/null +++ b/editor.lib/test/unit/src/org/netbeans/editor/TestingUndoableEditWrapper2.java @@ -0,0 +1,72 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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 2012 Sun Microsystems, Inc. + */ +package org.netbeans.editor; + +import javax.swing.text.Document; +import javax.swing.undo.CompoundEdit; +import javax.swing.undo.UndoableEdit; +import org.netbeans.spi.editor.document.UndoableEditWrapper; + +/** + * + * @author mmetelka + */ +//@MimeRegistration(mimeType="", service=UndoableEditWrapper.class) +public class TestingUndoableEditWrapper2 implements UndoableEditWrapper { + + @Override + public UndoableEdit wrap(UndoableEdit edit, Document doc) { + WrapCompoundEdit2 wrapEdit = new WrapCompoundEdit2(); + wrapEdit.addEdit(edit); + wrapEdit.end(); + return wrapEdit; + } + + static final class WrapCompoundEdit2 extends CompoundEdit { + + WrapCompoundEdit2() { + } + + } + + +} diff --git a/editor.lib/test/unit/src/org/netbeans/editor/UndoableEditWrapperTest.java b/editor.lib/test/unit/src/org/netbeans/editor/UndoableEditWrapperTest.java --- a/editor.lib/test/unit/src/org/netbeans/editor/UndoableEditWrapperTest.java +++ b/editor.lib/test/unit/src/org/netbeans/editor/UndoableEditWrapperTest.java @@ -44,12 +44,32 @@ package org.netbeans.editor; +import java.beans.PropertyChangeListener; +import java.beans.VetoableChangeListener; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import javax.swing.event.UndoableEditEvent; import javax.swing.event.UndoableEditListener; +import javax.swing.text.Document; +import javax.swing.text.EditorKit; import javax.swing.undo.UndoableEdit; import org.netbeans.api.editor.mimelookup.MimePath; import org.netbeans.api.editor.mimelookup.test.MockMimeLookup; import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.openide.text.NbDocumentRefactoringHack; +import org.openide.awt.UndoRedo; +import org.openide.cookies.EditorCookie; +import org.openide.text.CloneableEditorSupport; +import org.openide.util.Exceptions; +import org.openide.util.Lookup; +import org.openide.windows.CloneableOpenSupport; +import org.openide.windows.CloneableTopComponent; /** * @@ -57,6 +77,10 @@ */ public class UndoableEditWrapperTest extends NbTestCase { + static { + System.setProperty("org.openide.windows.DummyWindowManager.VISIBLE", "false"); + } + /** Creates a new instance of ZOrderTest */ public UndoableEditWrapperTest(String name) { super(name); @@ -64,17 +88,192 @@ public void testWrapping() throws Exception { MimePath mimePath = MimePath.EMPTY; - MockMimeLookup.setInstances(mimePath, new TestingUndoableEditWrapper()); - BaseDocument bDoc = new BaseDocument(false, ""); - bDoc.addUndoableEditListener(new UndoableEditListener() { - @Override - public void undoableEditHappened(UndoableEditEvent e) { - UndoableEdit edit = e.getEdit(); - assertEquals("Expected WrapCompoundEdit.class", - TestingUndoableEditWrapper.WrapCompoundEdit.class, edit.getClass()); + MockMimeLookup.setInstances(mimePath, new TestingUndoableEditWrapper(), new TestingUndoableEditWrapper2()); + CESEnv env = new CESEnv(); + Document doc = env.support.openDocument(); +// doc.addUndoableEditListener(new UndoableEditListener() { +// @Override +// public void undoableEditHappened(UndoableEditEvent e) { +// UndoableEdit edit = e.getEdit(); +// } +// }); + doc.insertString(0, "Test", null); + Class wrapEditClass = TestingUndoableEditWrapper.WrapCompoundEdit.class; + assertNotNull(NbDocumentRefactoringHack.getEditToBeUndoneOfType(env.support, wrapEditClass)); + Class wrapEditClass2 = TestingUndoableEditWrapper2.WrapCompoundEdit2.class; + assertNotNull(NbDocumentRefactoringHack.getEditToBeUndoneOfType(env.support, wrapEditClass2)); + + // A trick to get whole edit + UndoableEdit wholeEdit = NbDocumentRefactoringHack.getEditToBeUndoneOfType(env.support, UndoableEdit.class); + assertTrue(wholeEdit instanceof List); + @SuppressWarnings("unchecked") + List listEdit = (List) wholeEdit; + assertEquals(3, listEdit.size()); + assertEquals(wrapEditClass, listEdit.get(1).getClass()); + assertEquals(wrapEditClass2, listEdit.get(2).getClass()); + } + + private static final class CESEnv implements CloneableEditorSupport.Env { + + static final String mimeType = "text/plain"; + + /** the support to work with */ + transient final CES support; + + private transient String content = ""; // initial document content + private transient boolean modified = false; + /** if not null contains message why this document cannot be modified */ + private transient String cannotBeModified; + private transient Date date = new Date (); + private transient List/**/ propL = new ArrayList (); + private transient VetoableChangeListener vetoL; + + public CESEnv() { + support = new CES (this, Lookup.EMPTY); + } + + @Override + public synchronized void addPropertyChangeListener(PropertyChangeListener l) { + propL.add (l); + } + @Override + public synchronized void removePropertyChangeListener(PropertyChangeListener l) { + propL.remove (l); + } + + @Override + public synchronized void addVetoableChangeListener(VetoableChangeListener l) { + assertNull ("This is the first veto listener", vetoL); + vetoL = l; + } + + @Override + public void removeVetoableChangeListener(VetoableChangeListener l) { + assertEquals ("Removing the right veto one", vetoL, l); + vetoL = null; + } + + @Override + public CloneableOpenSupport findCloneableOpenSupport() { + return support; + } + + @Override + public String getMimeType() { + return mimeType; + } + + @Override + public Date getTime() { + return date; + } + + @Override + public InputStream inputStream() throws IOException { + return new ByteArrayInputStream (content.getBytes ()); + } + @Override + public OutputStream outputStream() throws IOException { + class ContentStream extends ByteArrayOutputStream { + @Override + public void close () throws IOException { + super.close (); + content = new String (toByteArray ()); + } } - }); - bDoc.insertString(0, "Test", null); + return new ContentStream (); + } + + @Override + public boolean isValid() { + return true; + } + + @Override + public boolean isModified() { + return modified; + } + + @Override + public void markModified() throws IOException { + if (cannotBeModified != null) { + final String notify = cannotBeModified; + IOException e = new IOException () { + @Override + public String getLocalizedMessage () { + return notify; + } + }; + Exceptions.attachLocalizedMessage(e, cannotBeModified); + throw e; + } + + modified = true; + } + + @Override + public void unmarkModified() { + modified = false; + } + + } + + /** Implementation of the CES */ + private static final class CES extends CloneableEditorSupport implements EditorCookie { + + public CES (CloneableEditorSupport.Env env, Lookup l) { + super (env, l); + } + + @Override + protected EditorKit createEditorKit () { + // Important to use NbLikeEditorKit since otherwise FilterDocument + // would be created with improper runAtomic() + return new MyKit (); + } + + public CloneableTopComponent.Ref getRef () { + return allEditors; + } + + @Override + protected String messageName() { + return "Name"; + } + + @Override + protected String messageOpened() { + return "Opened"; + } + + @Override + protected String messageOpening() { + return "Opening"; + } + + @Override + protected String messageSave() { + return "Save"; + } + + @Override + protected String messageToolTip() { + return "ToolTip"; + } + + public UndoRedo getUndoRedoPublic() { + return getUndoRedo(); + } + + } + + private static final class MyKit extends BaseKit { + + @Override + public String getContentType() { + return CESEnv.mimeType; + } + } } diff --git a/editor.lib2/src/org/netbeans/modules/editor/lib2/document/ListUndoableEdit.java b/editor.lib2/src/org/netbeans/modules/editor/lib2/document/ListUndoableEdit.java new file mode 100644 --- /dev/null +++ b/editor.lib2/src/org/netbeans/modules/editor/lib2/document/ListUndoableEdit.java @@ -0,0 +1,145 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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 2012 Sun Microsystems, Inc. + */ +package org.netbeans.modules.editor.lib2.document; + +import java.util.AbstractList; +import javax.swing.undo.CannotRedoException; +import javax.swing.undo.CannotUndoException; +import javax.swing.undo.UndoableEdit; + +/** + * Undoable edit that delegates all operation to its delegate. The delegate + * may be created by multiple wrapping of a document's undoable edit. This edit + * tracks all the wrappings by adding resulting edits into a list + * (because wrap edit does not allow to obtain the original edit being wrapped). + * + * @author Miloslav Metelka + */ +public class ListUndoableEdit extends AbstractList implements UndoableEdit { + + private UndoableEdit[] edits; + + public ListUndoableEdit(UndoableEdit e) { + edits = new UndoableEdit[] { e }; + } + + public ListUndoableEdit(UndoableEdit e0, UndoableEdit e1) { + edits = new UndoableEdit[] { e0, e1 }; + } + + public void setDelegate(UndoableEdit edit) { + UndoableEdit[] newEdits = new UndoableEdit[edits.length + 1]; + System.arraycopy(edits, 0, newEdits, 0, edits.length); + newEdits[edits.length] = edit; + edits = newEdits; + } + + @Override + public UndoableEdit get(int index) { + return edits[index]; + } + + @Override + public int size() { + return edits.length; + } + + public UndoableEdit delegate() { + return edits[edits.length - 1]; + } + + @Override + public void undo() throws CannotUndoException { + delegate().undo(); + } + + @Override + public boolean canUndo() { + return delegate().canUndo(); + } + + @Override + public void redo() throws CannotRedoException { + delegate().redo(); + } + + @Override + public boolean canRedo() { + return delegate().canRedo(); + } + + @Override + public void die() { + delegate().die(); + } + + @Override + public boolean addEdit(UndoableEdit anEdit) { + return delegate().addEdit(anEdit); + } + + @Override + public boolean replaceEdit(UndoableEdit anEdit) { + return delegate().replaceEdit(anEdit); + } + + @Override + public boolean isSignificant() { + return delegate().isSignificant(); + } + + @Override + public String getPresentationName() { + return delegate().getPresentationName(); + } + + @Override + public String getUndoPresentationName() { + return delegate().getUndoPresentationName(); + } + + @Override + public String getRedoPresentationName() { + return delegate().getRedoPresentationName(); + } + +} diff --git a/openide.text/src/org/openide/text/NbDocument.java b/openide.text/src/org/openide/text/NbDocument.java --- a/openide.text/src/org/openide/text/NbDocument.java +++ b/openide.text/src/org/openide/text/NbDocument.java @@ -46,6 +46,7 @@ import java.awt.Color; import java.awt.Component; import java.io.IOException; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -573,6 +574,15 @@ if (type.isInstance(edit)) { @SuppressWarnings("unchecked") T inst = (T) edit; return inst; + } else if (edit instanceof List) { + List listEdit = (List) edit; + for (int i = listEdit.size() -1; i >= 0; i--) { // Go from most wrapped back + edit = listEdit.get(i); + if (type.isInstance(edit)) { + @SuppressWarnings("unchecked") T inst = (T) edit; + return inst; + } + } } } return null;