diff options
6 files changed, 624 insertions, 593 deletions
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java index 2ec5368c5b1..79940e605e3 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewerUndoManager.java @@ -59,10 +59,11 @@ import org.eclipse.jface.dialogs.MessageDialog; */ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtension { + /** * Internal listener to mouse and key events. */ - class KeyAndMouseListener implements MouseListener, KeyListener { + private class KeyAndMouseListener implements MouseListener, KeyListener { /* * @see MouseListener#mouseDoubleClick @@ -110,10 +111,11 @@ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtensio } } + /** * Internal text input listener. */ - class TextInputListener implements ITextInputListener { + private class TextInputListener implements ITextInputListener { /* * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, org.eclipse.jface.text.IDocument) @@ -129,11 +131,12 @@ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtensio connectDocumentUndoManager(newInput); } } - + + /** * Internal document undo listener. */ - class DocumentUndoListener implements IDocumentUndoListener { + private class DocumentUndoListener implements IDocumentUndoListener { /* * @see org.eclipse.jface.text.IDocumentUndoListener#documentUndoNotification(DocumentUndoEvent) @@ -219,7 +222,6 @@ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtensio * Returns whether this undo manager is connected to a text viewer. * * @return <code>true</code> if connected, <code>false</code> otherwise - * @since 3.1 */ private boolean isConnected() { return fTextViewer != null && fDocumentUndoManager != null; @@ -281,7 +283,6 @@ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtensio * * @param title the dialog title * @param ex the exception - * @since 3.1 */ private void openErrorDialog(final String title, final Exception ex) { Shell shell= null; @@ -313,7 +314,7 @@ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtensio public void setMaximalUndoLevel(int undoLevel) { fUndoLevel= Math.max(0, undoLevel); if (isConnected()) { - fDocumentUndoManager.setUndoLimit(fUndoLevel); + fDocumentUndoManager.setMaximalUndoLevel(fUndoLevel); } } @@ -404,9 +405,8 @@ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtensio * * @param offset the offset of the range * @param length the length of the range - * @since 3.0 */ - protected void selectAndReveal(int offset, int length) { + private void selectAndReveal(int offset, int length) { if (fTextViewer instanceof ITextViewerExtension5) { ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer; extension.exposeModelRange(new Region(offset, length)); @@ -419,7 +419,6 @@ public class TextViewerUndoManager implements IUndoManager, IUndoManagerExtensio /* * @see org.eclipse.jface.text.IUndoManagerExtension#getUndoContext() - * @since 3.1 */ public IUndoContext getUndoContext() { if (isConnected()) { diff --git a/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java index 009e01b9de7..97d47007512 100644 --- a/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java +++ b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java @@ -13,6 +13,8 @@ package org.eclipse.text.undo; import java.util.ArrayList; import java.util.List; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.operations.AbstractOperation; import org.eclipse.core.commands.operations.IContextReplacingOperation; import org.eclipse.core.commands.operations.IOperationHistory; import org.eclipse.core.commands.operations.IOperationHistoryListener; @@ -22,7 +24,11 @@ import org.eclipse.core.commands.operations.ObjectUndoContext; import org.eclipse.core.commands.operations.OperationHistoryEvent; import org.eclipse.core.commands.operations.OperationHistoryFactory; +import org.eclipse.core.runtime.IAdaptable; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.Status; import org.eclipse.jface.text.Assert; import org.eclipse.jface.text.BadLocationException; @@ -58,6 +64,515 @@ import org.eclipse.jface.text.TextUtilities; * @since 3.2 */ public class DocumentUndoManager implements IDocumentUndoManager { + + + /** + * Represents an undo-able text change, described as the + * replacement of some preserved text with new text. + * <p> + * Based on the DefaultUndoManager.TextCommand from R3.1. + * </p> + */ + private static class UndoableTextChange extends AbstractOperation { + + /** The start index of the replaced text. */ + protected int fStart= -1; + + /** The end index of the replaced text. */ + protected int fEnd= -1; + + /** The newly inserted text. */ + protected String fText; + + /** The replaced text. */ + protected String fPreservedText; + + /** The undo modification stamp. */ + protected long fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; + + /** The redo modification stamp. */ + protected long fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; + + /** The undo manager that generated the change. */ + protected DocumentUndoManager fDocumentUndoManager; + + /** + * Creates a new text change. + * + * @param manager the undo manager for this change + */ + UndoableTextChange(DocumentUndoManager manager) { + super(UndoMessages.getString("DocumentUndoManager.operationLabel")); //$NON-NLS-1$ + this.fDocumentUndoManager= manager; + addContext(manager.getUndoContext()); + } + + /** + * Re-initializes this text change. + */ + protected void reinitialize() { + fStart= fEnd= -1; + fText= fPreservedText= null; + fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; + fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; + } + + /** + * Sets the start and the end index of this change. + * + * @param start the start index + * @param end the end index + */ + protected void set(int start, int end) { + fStart= start; + fEnd= end; + fText= null; + fPreservedText= null; + } + + /* + * @see org.eclipse.core.commands.operations.IUndoableOperation#dispose() + */ + public void dispose() { + reinitialize(); + } + + /** + * Undo the change described by this change. + */ + protected void undoTextChange() { + try { + if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) + ((IDocumentExtension4) fDocumentUndoManager.fDocument).replace(fStart, fText + .length(), fPreservedText, fUndoModificationStamp); + else + fDocumentUndoManager.fDocument.replace(fStart, fText.length(), + fPreservedText); + } catch (BadLocationException x) { + } + } + + /* + * @see org.eclipse.core.commands.operations.IUndoableOperation#canUndo() + */ + public boolean canUndo() { + if (isValid()) { + if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) { + long docStamp= ((IDocumentExtension4) fDocumentUndoManager.fDocument) + .getModificationStamp(); + + // Normal case: an undo is valid if its redo will restore + // document to its current modification stamp + boolean canUndo= docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP + || docStamp == getRedoModificationStamp(); + + /* + * Special case to check if the answer is false. If the last + * document change was empty, then the document's modification + * stamp was incremented but nothing was committed. The + * operation being queried has an older stamp. In this case + * only, the comparison is different. A sequence of document + * changes that include an empty change is handled correctly + * when a valid commit follows the empty change, but when + * #canUndo() is queried just after an empty change, we must + * special case the check. The check is very specific to prevent + * false positives. see + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=98245 + */ + if (!canUndo + && this == fDocumentUndoManager.fHistory + .getUndoOperation(fDocumentUndoManager.fUndoContext) + // this is the latest operation + && this != fDocumentUndoManager.fCurrent + // there is a more current operation not on the stack + && !fDocumentUndoManager.fCurrent.isValid() + // the current operation is not a valid document + // modification + && fDocumentUndoManager.fCurrent.fUndoModificationStamp != + // the invalid current operation has a document stamp + IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { + canUndo= fDocumentUndoManager.fCurrent.fRedoModificationStamp == docStamp; + } + /* + * When the composite is the current operation, it may hold the + * timestamp of a no-op change. We check this here rather than + * in an override of canUndo() in UndoableCompoundTextChange simply to + * keep all the special case checks in one place. + */ + if (!canUndo + && this == fDocumentUndoManager.fHistory + .getUndoOperation(fDocumentUndoManager.fUndoContext) + && // this is the latest operation + this instanceof UndoableCompoundTextChange + && this == fDocumentUndoManager.fCurrent + && // this is the current operation + this.fStart == -1 + && // the current operation text is not valid + fDocumentUndoManager.fCurrent.fRedoModificationStamp != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { + // but it has a redo stamp + canUndo= fDocumentUndoManager.fCurrent.fRedoModificationStamp == docStamp; + } + + } + // if there is no timestamp to check, simply return true per the + // 3.0.1 behavior + return true; + } + return false; + } + + /* + * @see org.eclipse.core.commands.operations.IUndoableOperation#canRedo() + */ + public boolean canRedo() { + if (isValid()) { + if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) { + long docStamp= ((IDocumentExtension4) fDocumentUndoManager.fDocument) + .getModificationStamp(); + return docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP + || docStamp == getUndoModificationStamp(); + } + // if there is no timestamp to check, simply return true per the + // 3.0.1 behavior + return true; + } + return false; + } + + /* + * @see org.eclipse.core.commands.operations.IUndoableOperation#canExecute() + */ + public boolean canExecute() { + return fDocumentUndoManager.isConnected(); + } + + /* + * @see org.eclipse.core.commands.operations.IUndoableOperation.IUndoableOperation#execute(IProgressMonitor, IAdaptable) + */ + public IStatus execute(IProgressMonitor monitor, IAdaptable uiInfo) { + // Text changes execute as they are typed, so executing one has no + // effect. + return Status.OK_STATUS; + } + + /** + * {@inheritDoc} + * Notifies clients about the undo. + */ + public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) { + if (isValid()) { + fDocumentUndoManager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, false); + undoTextChange(); + fDocumentUndoManager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.UNDONE, false); + return Status.OK_STATUS; + } + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + /** + * Re-applies the change described by this change. + */ + protected void redoTextChange() { + try { + if (fDocumentUndoManager.fDocument instanceof IDocumentExtension4) + ((IDocumentExtension4) fDocumentUndoManager.fDocument).replace(fStart, fEnd - fStart, fText, fRedoModificationStamp); + else + fDocumentUndoManager.fDocument.replace(fStart, fEnd - fStart, fText); + } catch (BadLocationException x) { + } + } + + /** + * Re-applies the change described by this change that was previously + * undone. Also notifies clients about the redo. + * + * @param monitor the progress monitor to use if necessary + * @param uiInfo an adaptable that can provide UI info if needed + * @return the status + */ + public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) { + if (isValid()) { + fDocumentUndoManager.fireDocumentUndo(fStart, fText, fPreservedText, uiInfo, DocumentUndoEvent.ABOUT_TO_REDO, false); + redoTextChange(); + fDocumentUndoManager.fireDocumentUndo(fStart, fText, fPreservedText, uiInfo, DocumentUndoEvent.REDONE, false); + return Status.OK_STATUS; + } + return IOperationHistory.OPERATION_INVALID_STATUS; + } + + /** + * Update the change in response to a commit. + */ + + protected void updateTextChange() { + fText= fDocumentUndoManager.fTextBuffer.toString(); + fDocumentUndoManager.fTextBuffer.setLength(0); + fPreservedText= fDocumentUndoManager.fPreservedTextBuffer.toString(); + fDocumentUndoManager.fPreservedTextBuffer.setLength(0); + } + + /** + * Creates a new uncommitted text change depending on whether a compound + * change is currently being executed. + * + * @return a new, uncommitted text change or a compound text change + */ + protected UndoableTextChange createCurrent() { + if (fDocumentUndoManager.fFoldingIntoCompoundChange) + return new UndoableCompoundTextChange(fDocumentUndoManager); + return new UndoableTextChange(fDocumentUndoManager); + } + + /** + * Commits the current change into this one. + */ + protected void commit() { + if (fStart < 0) { + if (fDocumentUndoManager.fFoldingIntoCompoundChange) { + fDocumentUndoManager.fCurrent= createCurrent(); + } else { + reinitialize(); + } + } else { + updateTextChange(); + fDocumentUndoManager.fCurrent= createCurrent(); + } + } + + /** + * Updates the text from the buffers without resetting the buffers or adding + * anything to the stack. + */ + protected void pretendCommit() { + if (fStart > -1) { + fText= fDocumentUndoManager.fTextBuffer.toString(); + fPreservedText= fDocumentUndoManager.fPreservedTextBuffer.toString(); + } + } + + /** + * Attempt a commit of this change and answer true if a new fCurrent was + * created as a result of the commit. + * + * @return <code>true</code> if the change was committed and created + * a new <code>fCurrent</code>, <code>false</code> if not + */ + protected boolean attemptCommit() { + pretendCommit(); + if (isValid()) { + fDocumentUndoManager.commit(); + return true; + } + return false; + } + + /** + * Checks whether this text change is valid for undo or redo. + * + * @return <code>true</code> if the change is valid for undo or redo + */ + protected boolean isValid() { + return fStart > -1 && fEnd > -1 && fText != null; + } + + /* + * @see java.lang.Object#toString() + */ + public String toString() { + String delimiter= ", "; //$NON-NLS-1$ + StringBuffer text= new StringBuffer(super.toString()); + text.append("\n"); //$NON-NLS-1$ + text.append(this.getClass().getName()); + text.append(" undo modification stamp: "); //$NON-NLS-1$ + text.append(fUndoModificationStamp); + text.append(" redo modification stamp: "); //$NON-NLS-1$ + text.append(fRedoModificationStamp); + text.append(" start: "); //$NON-NLS-1$ + text.append(fStart); + text.append(delimiter); + text.append("end: "); //$NON-NLS-1$ + text.append(fEnd); + text.append(delimiter); + text.append("text: '"); //$NON-NLS-1$ + text.append(fText); + text.append('\''); + text.append(delimiter); + text.append("preservedText: '"); //$NON-NLS-1$ + text.append(fPreservedText); + text.append('\''); + return text.toString(); + } + + /** + * Return the undo modification stamp + * + * @return the undo modification stamp for this change + */ + protected long getUndoModificationStamp() { + return fUndoModificationStamp; + } + + /** + * Return the redo modification stamp + * + * @return the redo modification stamp for this change + */ + protected long getRedoModificationStamp() { + return fRedoModificationStamp; + } + } + + + /** + * Represents an undo-able text change consisting of several individual + * changes. + */ + private static class UndoableCompoundTextChange extends UndoableTextChange { + + /** The list of individual changes */ + private List fChanges= new ArrayList(); + + /** + * Creates a new compound text change. + * + * @param manager + * the undo manager for this change + */ + UndoableCompoundTextChange(DocumentUndoManager manager) { + super(manager); + } + + /** + * Adds a new individual change to this compound change. + * + * @param change the change to be added + */ + protected void add(UndoableTextChange change) { + fChanges.add(change); + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#undo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable) + */ + public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) { + + int size= fChanges.size(); + if (size > 0) { + UndoableTextChange c; + + c= (UndoableTextChange) fChanges.get(0); + fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, true); + + for (int i= size - 1; i >= 0; --i) { + c= (UndoableTextChange) fChanges.get(i); + c.undoTextChange(); + } + fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo, + DocumentUndoEvent.UNDONE, true); + } + return Status.OK_STATUS; + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#redo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable) + */ + public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) { + + int size= fChanges.size(); + if (size > 0) { + + UndoableTextChange c; + c= (UndoableTextChange) fChanges.get(size - 1); + fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo, DocumentUndoEvent.ABOUT_TO_REDO, true); + + for (int i= 0; i <= size - 1; ++i) { + c= (UndoableTextChange) fChanges.get(i); + c.redoTextChange(); + } + fDocumentUndoManager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo, + DocumentUndoEvent.REDONE, true); + } + + return Status.OK_STATUS; + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#updateTextChange() + */ + protected void updateTextChange() { + // first gather the data from the buffers + super.updateTextChange(); + + // the result of the update is stored as a child change + UndoableTextChange c= new UndoableTextChange(fDocumentUndoManager); + c.fStart= fStart; + c.fEnd= fEnd; + c.fText= fText; + c.fPreservedText= fPreservedText; + c.fUndoModificationStamp= fUndoModificationStamp; + c.fRedoModificationStamp= fRedoModificationStamp; + add(c); + + // clear out all indexes now that the child is added + reinitialize(); + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#createCurrent() + */ + protected UndoableTextChange createCurrent() { + + if (!fDocumentUndoManager.fFoldingIntoCompoundChange) + return new UndoableTextChange(fDocumentUndoManager); + + reinitialize(); + return this; + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#commit() + */ + protected void commit() { + // if there is pending data, update the text change + if (fStart > -1) + updateTextChange(); + fDocumentUndoManager.fCurrent= createCurrent(); + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#isValid() + */ + protected boolean isValid() { + return fStart > -1 || fChanges.size() > 0; + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#getUndoModificationStamp() + */ + protected long getUndoModificationStamp() { + if (fStart > -1) + return super.getUndoModificationStamp(); + else if (fChanges.size() > 0) + return ((UndoableTextChange) fChanges.get(0)) + .getUndoModificationStamp(); + + return fUndoModificationStamp; + } + + /* + * @see org.eclipse.text.undo.UndoableTextChange#getRedoModificationStamp() + */ + protected long getRedoModificationStamp() { + if (fStart > -1) + return super.getRedoModificationStamp(); + else if (fChanges.size() > 0) + return ((UndoableTextChange) fChanges.get(fChanges.size() - 1)) + .getRedoModificationStamp(); + + return fRedoModificationStamp; + } + } + /** * Internal listener to document changes. @@ -124,7 +639,8 @@ public class DocumentUndoManager implements IDocumentUndoManager { /* * @see IOperationHistoryListener */ - class HistoryListener implements IOperationHistoryListener { + private class HistoryListener implements IOperationHistoryListener { + private IUndoableOperation fOperation; public void historyNotification(final OperationHistoryEvent event) { @@ -172,17 +688,17 @@ public class DocumentUndoManager implements IDocumentUndoManager { /** * The undo context for this document undo manager. */ - ObjectUndoContext fUndoContext; + private ObjectUndoContext fUndoContext; /** * The document whose changes are being tracked. */ - IDocument fDocument; + private IDocument fDocument; /** * The currently constructed edit. */ - UndoableTextChange fCurrent; + private UndoableTextChange fCurrent; /** * The internal document listener. @@ -192,12 +708,12 @@ public class DocumentUndoManager implements IDocumentUndoManager { /** * Indicates whether the current change belongs to a compound change. */ - boolean fFoldingIntoCompoundChange= false; + private boolean fFoldingIntoCompoundChange= false; /** * The operation history being used to store the undo history. */ - IOperationHistory fHistory; + private IOperationHistory fHistory; /** * The operation history listener used for managing undo and redo before and @@ -215,17 +731,17 @@ public class DocumentUndoManager implements IDocumentUndoManager { /** * The document modification stamp for redo. */ - protected long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; + private long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; /** * Text buffer to collect viewer content which has been replaced */ - StringBuffer fPreservedTextBuffer; + private StringBuffer fPreservedTextBuffer; /** * The document modification stamp for undo. */ - protected long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; + private long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; /** * The last delete text edit. @@ -235,7 +751,7 @@ public class DocumentUndoManager implements IDocumentUndoManager { /** * Text buffer to collect text which is inserted into the viewer */ - StringBuffer fTextBuffer; + private StringBuffer fTextBuffer; /** Indicates inserting state. */ private boolean fInserting= false; @@ -305,6 +821,46 @@ public class DocumentUndoManager implements IDocumentUndoManager { } fCurrent.commit(); } + + /* + * @see org.eclipse.text.undo.IDocumentUndoManager#reset() + */ + public void reset() { + if (isConnected()) { + shutdown(); + initialize(); + } + } + + /* + * @see org.eclipse.text.undo.IDocumentUndoManager#redoable() + */ + public boolean redoable() { + return OperationHistoryFactory.getOperationHistory().canRedo(fUndoContext); + } + + /* + * @see org.eclipse.text.undo.IDocumentUndoManager#undoable() + */ + public boolean undoable() { + return OperationHistoryFactory.getOperationHistory().canUndo(fUndoContext); + } + + /* + * @see org.eclipse.text.undo.IDocumentUndoManager#undo() + */ + public void redo() throws ExecutionException { + if (isConnected() && redoable()) + OperationHistoryFactory.getOperationHistory().redo(getUndoContext(), null, null); + } + + /* + * @see org.eclipse.text.undo.IDocumentUndoManager#undo() + */ + public void undo() throws ExecutionException { + if (undoable()) + OperationHistoryFactory.getOperationHistory().undo(fUndoContext, null, null); + } /* * @see org.eclipse.jface.text.IDocumentUndoManager#connect(java.lang.Object) @@ -350,7 +906,7 @@ public class DocumentUndoManager implements IDocumentUndoManager { /* * @see org.eclipse.jface.text.IDocumentUndoManager#setUndoLimit(int) */ - public void setUndoLimit(int undoLimit) { + public void setMaximalUndoLevel(int undoLimit) { fHistory.setLimit(fUndoContext, undoLimit); } @@ -672,8 +1228,7 @@ public class DocumentUndoManager implements IDocumentUndoManager { return false; return !fConnected.isEmpty(); } - - + /* * @see org.eclipse.jface.text.IDocumentUndoManager#transferUndoHistory(IDocumentUndoManager) */ @@ -692,7 +1247,7 @@ public class DocumentUndoManager implements IDocumentUndoManager { } // Now update the manager that owns the text edit. if (op instanceof UndoableTextChange) { - ((UndoableTextChange)op).manager= this; + ((UndoableTextChange)op).fDocumentUndoManager= this; } } diff --git a/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java index 404eb3c7372..50b38e3d5a7 100644 --- a/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java +++ b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java @@ -30,15 +30,12 @@ import org.eclipse.jface.text.IDocument; * <p> * This class is not intended to be subclassed. * </p> - * <p> - * XXX: This is work in progress and can change anytime until API for 3.2 is frozen. - * </p> * * @since 3.2 */ public final class DocumentUndoManagerRegistry { - final private static class Record { + private static final class Record { public Record(IDocument document) { count= 0; undoManager= new DocumentUndoManager(document); diff --git a/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java b/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java index 080d3619763..3fc75a7f37d 100644 --- a/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java +++ b/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.text.undo; +import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.operations.IUndoContext; /** @@ -35,8 +36,6 @@ import org.eclipse.core.commands.operations.IUndoContext; * * @see DocumentUndoManagerRegistry * @see IDocumentUndoListener - * @see UndoableTextChange - * @see UndoableCompoundTextChange * @see org.eclipse.jface.text.IDocument * @since 3.2 */ @@ -59,7 +58,7 @@ public interface IDocumentUndoManager { * * @param listener the document undo listener to be added as a listener */ - public abstract void addDocumentUndoListener(IDocumentUndoListener listener); + void addDocumentUndoListener(IDocumentUndoListener listener); /** * Removes the specified listener from the list of document undo listeners. @@ -69,19 +68,19 @@ public interface IDocumentUndoManager { * * @param listener the document undo listener to be removed */ - public abstract void removeDocumentUndoListener(IDocumentUndoListener listener); + void removeDocumentUndoListener(IDocumentUndoListener listener); /** * Returns the undo context registered for this document * * @return the undo context registered for this document */ - public abstract IUndoContext getUndoContext(); + IUndoContext getUndoContext(); /** * Closes the currently open text edit and open a new one. */ - public abstract void commit(); + void commit(); /** * Connects to the undo manager. Used to signify that a client is monitoring @@ -90,7 +89,7 @@ public interface IDocumentUndoManager { * * @param client the object connecting to the undo manager */ - public abstract void connect(Object client); + void connect(Object client); /** * Disconnects from the undo manager. Used to signify that a client is no @@ -100,20 +99,20 @@ public interface IDocumentUndoManager { * * @param client the object disconnecting from the undo manager */ - public abstract void disconnect(Object client); + void disconnect(Object client); /** * Signals the undo manager that all subsequent changes until * <code>endCompoundChange</code> is called are to be undone in one piece. */ - public abstract void beginCompoundChange(); + void beginCompoundChange(); /** * Signals the undo manager that the sequence of changes which started with * <code>beginCompoundChange</code> has been finished. All subsequent * changes are considered to be individually undo-able. */ - public abstract void endCompoundChange(); + void endCompoundChange(); /** * Sets the limit of the undo history to the specified value. The provided @@ -121,7 +120,42 @@ public interface IDocumentUndoManager { * * @param undoLimit the length of this undo manager's history */ - public abstract void setUndoLimit(int undoLimit); + void setMaximalUndoLevel(int undoLimit); + + /** + * Resets the history of the undo manager. After that call, + * there aren't any undo-able or redo-able text changes. + */ + void reset(); + + /** + * Returns whether at least one text change can be rolled back. + * + * @return <code>true</code> if at least one text change can be rolled back + */ + boolean undoable(); + + /** + * Returns whether at least one text change can be repeated. A text change + * can be repeated only if it was executed and rolled back. + * + * @return <code>true</code> if at least on text change can be repeated + */ + boolean redoable(); + + /** + * Rolls back the most recently executed text change. + * + * @throws ExecutionException if an exception occurred during undo + */ + void undo() throws ExecutionException; + + /** + * Repeats the most recently rolled back text change. + * + * @throws ExecutionException if an exception occurred during redo + */ + void redo() throws ExecutionException; /** * Transfers the undo history from the specified document undo manager to diff --git a/org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java b/org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java deleted file mode 100644 index d7ed61d2aa6..00000000000 --- a/org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java +++ /dev/null @@ -1,173 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.text.undo; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; - -/** - * Represents an undo-able text change consisting of several individual - * changes. - * - * @see org.eclipse.text.undo.UndoableTextChange - * @see org.eclipse.text.undo.DocumentUndoManager - * @since 3.2 - */ -class UndoableCompoundTextChange extends UndoableTextChange { - - /** The list of individual changes */ - private List fChanges= new ArrayList(); - - /** - * Creates a new compound text change. - * - * @param manager - * the undo manager for this change - */ - UndoableCompoundTextChange(DocumentUndoManager manager) { - super(manager); - } - - /** - * Adds a new individual change to this compound change. - * - * @param change the change to be added - */ - protected void add(UndoableTextChange change) { - fChanges.add(change); - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#undo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable) - */ - public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) { - - int size= fChanges.size(); - if (size > 0) { - UndoableTextChange c; - - c= (UndoableTextChange) fChanges.get(0); - manager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, true); - - for (int i= size - 1; i >= 0; --i) { - c= (UndoableTextChange) fChanges.get(i); - c.undoTextChange(); - } - manager.fireDocumentUndo(c.fStart, c.fPreservedText, c.fText, uiInfo, - DocumentUndoEvent.UNDONE, true); - } - return Status.OK_STATUS; - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#redo(org.eclipse.core.runtime.IProgressMonitor, org.eclipse.core.runtime.IAdaptable) - */ - public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) { - - int size= fChanges.size(); - if (size > 0) { - - UndoableTextChange c; - c= (UndoableTextChange) fChanges.get(size - 1); - manager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo, DocumentUndoEvent.ABOUT_TO_REDO, true); - - for (int i= 0; i <= size - 1; ++i) { - c= (UndoableTextChange) fChanges.get(i); - c.redoTextChange(); - } - manager.fireDocumentUndo(c.fStart, c.fText, c.fPreservedText, uiInfo, - DocumentUndoEvent.REDONE, true); - } - - return Status.OK_STATUS; - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#updateTextChange() - */ - protected void updateTextChange() { - // first gather the data from the buffers - super.updateTextChange(); - - // the result of the update is stored as a child change - UndoableTextChange c= new UndoableTextChange(manager); - c.fStart= fStart; - c.fEnd= fEnd; - c.fText= fText; - c.fPreservedText= fPreservedText; - c.fUndoModificationStamp= fUndoModificationStamp; - c.fRedoModificationStamp= fRedoModificationStamp; - add(c); - - // clear out all indexes now that the child is added - reinitialize(); - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#createCurrent() - */ - protected UndoableTextChange createCurrent() { - - if (!manager.fFoldingIntoCompoundChange) - return new UndoableTextChange(manager); - - reinitialize(); - return this; - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#commit() - */ - protected void commit() { - // if there is pending data, update the text change - if (fStart > -1) - updateTextChange(); - manager.fCurrent= createCurrent(); - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#isValid() - */ - protected boolean isValid() { - return fStart > -1 || fChanges.size() > 0; - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#getUndoModificationStamp() - */ - protected long getUndoModificationStamp() { - if (fStart > -1) - return super.getUndoModificationStamp(); - else if (fChanges.size() > 0) - return ((UndoableTextChange) fChanges.get(0)) - .getUndoModificationStamp(); - - return fUndoModificationStamp; - } - - /* - * @see org.eclipse.text.undo.UndoableTextChange#getRedoModificationStamp() - */ - protected long getRedoModificationStamp() { - if (fStart > -1) - return super.getRedoModificationStamp(); - else if (fChanges.size() > 0) - return ((UndoableTextChange) fChanges.get(fChanges.size() - 1)) - .getRedoModificationStamp(); - - return fRedoModificationStamp; - } -} diff --git a/org.eclipse.text/src/org/eclipse/text/undo/UndoableTextChange.java b/org.eclipse.text/src/org/eclipse/text/undo/UndoableTextChange.java deleted file mode 100644 index 21574aa1c42..00000000000 --- a/org.eclipse.text/src/org/eclipse/text/undo/UndoableTextChange.java +++ /dev/null @@ -1,381 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2006 IBM Corporation and others. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * IBM Corporation - initial API and implementation - *******************************************************************************/ -package org.eclipse.text.undo; - -import org.eclipse.core.commands.operations.AbstractOperation; -import org.eclipse.core.commands.operations.IOperationHistory; - -import org.eclipse.core.runtime.IAdaptable; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; - -import org.eclipse.jface.text.BadLocationException; -import org.eclipse.jface.text.IDocumentExtension4; - -/** - * Represents an undo-able text change, described as the - * replacement of some preserved text with new text. - * <p> - * Based on the DefaultUndoManager.TextCommand from R3.1. - * </p> - * - * @see org.eclipse.text.undo.DocumentUndoManager - * @since 3.2 - */ -class UndoableTextChange extends AbstractOperation { - - /** The start index of the replaced text. */ - protected int fStart= -1; - - /** The end index of the replaced text. */ - protected int fEnd= -1; - - /** The newly inserted text. */ - protected String fText; - - /** The replaced text. */ - protected String fPreservedText; - - /** The undo modification stamp. */ - protected long fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; - - /** The redo modification stamp. */ - protected long fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; - - /** The undo manager that generated the change. */ - protected DocumentUndoManager manager; - - /** - * Creates a new text change. - * - * @param manager the undo manager for this change - */ - UndoableTextChange(DocumentUndoManager manager) { - super(UndoMessages.getString("DocumentUndoManager.operationLabel")); //$NON-NLS-1$ - this.manager= manager; - addContext(manager.getUndoContext()); - } - - /** - * Re-initializes this text change. - */ - protected void reinitialize() { - fStart= fEnd= -1; - fText= fPreservedText= null; - fUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; - fRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP; - } - - /** - * Sets the start and the end index of this change. - * - * @param start the start index - * @param end the end index - */ - protected void set(int start, int end) { - fStart= start; - fEnd= end; - fText= null; - fPreservedText= null; - } - - /* - * @see org.eclipse.core.commands.operations.IUndoableOperation#dispose() - */ - public void dispose() { - reinitialize(); - } - - /** - * Undo the change described by this change. - */ - protected void undoTextChange() { - try { - if (manager.fDocument instanceof IDocumentExtension4) - ((IDocumentExtension4) manager.fDocument).replace(fStart, fText - .length(), fPreservedText, fUndoModificationStamp); - else - manager.fDocument.replace(fStart, fText.length(), - fPreservedText); - } catch (BadLocationException x) { - } - } - - /* - * @see org.eclipse.core.commands.operations.IUndoableOperation#canUndo() - */ - public boolean canUndo() { - if (isValid()) { - if (manager.fDocument instanceof IDocumentExtension4) { - long docStamp= ((IDocumentExtension4) manager.fDocument) - .getModificationStamp(); - - // Normal case: an undo is valid if its redo will restore - // document to its current modification stamp - boolean canUndo= docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP - || docStamp == getRedoModificationStamp(); - - /* - * Special case to check if the answer is false. If the last - * document change was empty, then the document's modification - * stamp was incremented but nothing was committed. The - * operation being queried has an older stamp. In this case - * only, the comparison is different. A sequence of document - * changes that include an empty change is handled correctly - * when a valid commit follows the empty change, but when - * #canUndo() is queried just after an empty change, we must - * special case the check. The check is very specific to prevent - * false positives. see - * https://bugs.eclipse.org/bugs/show_bug.cgi?id=98245 - */ - if (!canUndo - && this == manager.fHistory - .getUndoOperation(manager.fUndoContext) - // this is the latest operation - && this != manager.fCurrent - // there is a more current operation not on the stack - && !manager.fCurrent.isValid() - // the current operation is not a valid document - // modification - && manager.fCurrent.fUndoModificationStamp != - // the invalid current operation has a document stamp - IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { - canUndo= manager.fCurrent.fRedoModificationStamp == docStamp; - } - /* - * When the composite is the current operation, it may hold the - * timestamp of a no-op change. We check this here rather than - * in an override of canUndo() in UndoableCompoundTextChange simply to - * keep all the special case checks in one place. - */ - if (!canUndo - && this == manager.fHistory - .getUndoOperation(manager.fUndoContext) - && // this is the latest operation - this instanceof UndoableCompoundTextChange - && this == manager.fCurrent - && // this is the current operation - this.fStart == -1 - && // the current operation text is not valid - manager.fCurrent.fRedoModificationStamp != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) { - // but it has a redo stamp - canUndo= manager.fCurrent.fRedoModificationStamp == docStamp; - } - - } - // if there is no timestamp to check, simply return true per the - // 3.0.1 behavior - return true; - } - return false; - } - - /* - * @see org.eclipse.core.commands.operations.IUndoableOperation#canRedo() - */ - public boolean canRedo() { - if (isValid()) { - if (manager.fDocument instanceof IDocumentExtension4) { - long docStamp= ((IDocumentExtension4) manager.fDocument) - .getModificationStamp(); - return docStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP - || docStamp == getUndoModificationStamp(); - } - // if there is no timestamp to check, simply return true per the - // 3.0.1 behavior - return true; - } - return false; - } - - /* - * @see org.eclipse.core.commands.operations.IUndoableOperation#canExecute() - */ - public boolean canExecute() { - return manager.isConnected(); - } - - /* - * @see org.eclipse.core.commands.operations.IUndoableOperation.IUndoableOperation#execute(IProgressMonitor, IAdaptable) - */ - public IStatus execute(IProgressMonitor monitor, IAdaptable uiInfo) { - // Text changes execute as they are typed, so executing one has no - // effect. - return Status.OK_STATUS; - } - - /** - * {@inheritDoc} - * Notifies clients about the undo. - */ - public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) { - if (isValid()) { - manager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.ABOUT_TO_UNDO, false); - undoTextChange(); - manager.fireDocumentUndo(fStart, fPreservedText, fText, uiInfo, DocumentUndoEvent.UNDONE, false); - return Status.OK_STATUS; - } - return IOperationHistory.OPERATION_INVALID_STATUS; - } - - /** - * Re-applies the change described by this change. - */ - protected void redoTextChange() { - try { - if (manager.fDocument instanceof IDocumentExtension4) - ((IDocumentExtension4) manager.fDocument).replace(fStart, fEnd - fStart, fText, fRedoModificationStamp); - else - manager.fDocument.replace(fStart, fEnd - fStart, fText); - } catch (BadLocationException x) { - } - } - - /** - * Re-applies the change described by this change that was previously - * undone. Also notifies clients about the redo. - * - * @param monitor the progress monitor to use if necessary - * @param uiInfo an adaptable that can provide UI info if needed - * @return the status - */ - public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) { - if (isValid()) { - manager.fireDocumentUndo(fStart, fText, fPreservedText, uiInfo, DocumentUndoEvent.ABOUT_TO_REDO, false); - redoTextChange(); - manager.fireDocumentUndo(fStart, fText, fPreservedText, uiInfo, DocumentUndoEvent.REDONE, false); - return Status.OK_STATUS; - } - return IOperationHistory.OPERATION_INVALID_STATUS; - } - - /** - * Update the change in response to a commit. - */ - - protected void updateTextChange() { - fText= manager.fTextBuffer.toString(); - manager.fTextBuffer.setLength(0); - fPreservedText= manager.fPreservedTextBuffer.toString(); - manager.fPreservedTextBuffer.setLength(0); - } - - /** - * Creates a new uncommitted text change depending on whether a compound - * change is currently being executed. - * - * @return a new, uncommitted text change or a compound text change - */ - protected UndoableTextChange createCurrent() { - if (manager.fFoldingIntoCompoundChange) - return new UndoableCompoundTextChange(manager); - return new UndoableTextChange(manager); - } - - /** - * Commits the current change into this one. - */ - protected void commit() { - if (fStart < 0) { - if (manager.fFoldingIntoCompoundChange) { - manager.fCurrent= createCurrent(); - } else { - reinitialize(); - } - } else { - updateTextChange(); - manager.fCurrent= createCurrent(); - } - } - - /** - * Updates the text from the buffers without resetting the buffers or adding - * anything to the stack. - */ - protected void pretendCommit() { - if (fStart > -1) { - fText= manager.fTextBuffer.toString(); - fPreservedText= manager.fPreservedTextBuffer.toString(); - } - } - - /** - * Attempt a commit of this change and answer true if a new fCurrent was - * created as a result of the commit. - * - * @return <code>true</code> if the change was committed and created - * a new <code>fCurrent</code>, <code>false</code> if not - */ - protected boolean attemptCommit() { - pretendCommit(); - if (isValid()) { - manager.commit(); - return true; - } - return false; - } - - /** - * Checks whether this text change is valid for undo or redo. - * - * @return <code>true</code> if the change is valid for undo or redo - */ - protected boolean isValid() { - return fStart > -1 && fEnd > -1 && fText != null; - } - - /* - * @see java.lang.Object#toString() - */ - public String toString() { - String delimiter= ", "; //$NON-NLS-1$ - StringBuffer text= new StringBuffer(super.toString()); - text.append("\n"); //$NON-NLS-1$ - text.append(this.getClass().getName()); - text.append(" undo modification stamp: "); //$NON-NLS-1$ - text.append(fUndoModificationStamp); - text.append(" redo modification stamp: "); //$NON-NLS-1$ - text.append(fRedoModificationStamp); - text.append(" start: "); //$NON-NLS-1$ - text.append(fStart); - text.append(delimiter); - text.append("end: "); //$NON-NLS-1$ - text.append(fEnd); - text.append(delimiter); - text.append("text: '"); //$NON-NLS-1$ - text.append(fText); - text.append('\''); - text.append(delimiter); - text.append("preservedText: '"); //$NON-NLS-1$ - text.append(fPreservedText); - text.append('\''); - return text.toString(); - } - - /** - * Return the undo modification stamp - * - * @return the undo modification stamp for this change - */ - protected long getUndoModificationStamp() { - return fUndoModificationStamp; - } - - /** - * Return the redo modification stamp - * - * @return the redo modification stamp for this change - */ - protected long getRedoModificationStamp() { - return fRedoModificationStamp; - } -} |