Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDani Megert2006-01-24 05:29:21 -0500
committerDani Megert2006-01-24 05:29:21 -0500
commit9e0a0e4bf90c5b897ff8ec6f3cdcd98287308bc7 (patch)
tree74479a6ba0ba1146a3fa9447ce98ee3a06da84aa /org.eclipse.text/src/org/eclipse/text/undo
parentecd8bd57e20521183406e6da92880619e69ed3f0 (diff)
downloadeclipse.platform.text-9e0a0e4bf90c5b897ff8ec6f3cdcd98287308bc7.tar.gz
eclipse.platform.text-9e0a0e4bf90c5b897ff8ec6f3cdcd98287308bc7.tar.xz
eclipse.platform.text-9e0a0e4bf90c5b897ff8ec6f3cdcd98287308bc7.zip
First cut of fix for bug 89599: [api][typing] Text Editor Undo stack (context) should be keyed of common document
Diffstat (limited to 'org.eclipse.text/src/org/eclipse/text/undo')
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoEvent.java168
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java719
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java111
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoListener.java41
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java139
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.java46
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.properties12
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java181
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/UndoableTextChange.java384
-rw-r--r--org.eclipse.text/src/org/eclipse/text/undo/package.html14
10 files changed, 1815 insertions, 0 deletions
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoEvent.java b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoEvent.java
new file mode 100644
index 000000000..3b13af2a7
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoEvent.java
@@ -0,0 +1,168 @@
+/*******************************************************************************
+ * 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.runtime.IAdaptable;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.IDocument;
+
+/**
+ * Describes document changes initiated by undo or redo.
+ * <p>
+ * Clients are not supposed to subclass or create instances of this class.
+ * </p>
+ * <p>
+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen.
+ * </p>
+ *
+ * @see IDocumentUndoManager
+ * @see IDocumentUndoListener
+ * @since 3.2
+ */
+public class DocumentUndoEvent {
+
+ /**
+ * Indicates that the described document event is about to be
+ * undone.
+ */
+ public static final int ABOUT_TO_UNDO= 1 << 0;
+
+ /**
+ * Indicates that the described document event is about to be
+ * redone.
+ */
+ public static final int ABOUT_TO_REDO= 1 << 1;
+
+ /**
+ * Indicates that the described document event has been undone.
+ */
+ public static final int UNDONE= 1 << 2;
+
+ /**
+ * Indicates that the described document event has been redone.
+ */
+ public static final int REDONE= 1 << 3;
+
+ /**
+ * Indicates that the described document event is a compound undo
+ * or redo event.
+ */
+ public static final int COMPOUND= 1 << 4;
+
+ /** The changed document. */
+ private IDocument fDocument;
+
+ /** The document offset where the change begins. */
+ private int fOffset;
+
+ /** Text inserted into the document. */
+ private String fText;
+
+ /** Text replaced in the document. */
+ private String fPreservedText;
+
+ /** Bit mask of event types describing the event */
+ private int fEventType;
+
+ /** The adaptable describing UI context of the triggering undo. */
+ private IAdaptable fInfoAdapter;
+
+ /**
+ * Creates a new document event.
+ *
+ * @param doc the changed document
+ * @param offset the offset of the replaced text
+ * @param text the substitution text
+ * @param preservedText the replaced text
+ * @param eventType a bit mask describing the type(s) of event
+ * @param uiInfo an adapter providing information about the triggering undo or redo or <code>null</code>
+ */
+ DocumentUndoEvent(IDocument doc, int offset, String text, String preservedText, int eventType, IAdaptable uiInfo) {
+
+ Assert.isNotNull(doc);
+ Assert.isTrue(offset >= 0);
+
+ fDocument= doc;
+ fOffset= offset;
+ fText= text;
+ fPreservedText= preservedText;
+ fEventType= eventType;
+ fInfoAdapter= uiInfo;
+ }
+
+ /**
+ * Returns the changed document.
+ *
+ * @return the changed document
+ */
+ public IDocument getDocument() {
+ return fDocument;
+ }
+
+ /**
+ * Returns the offset of the change.
+ *
+ * @return the offset of the change
+ */
+ public int getOffset() {
+ return fOffset;
+ }
+
+ /**
+ * Returns the text that has been inserted.
+ *
+ * @return the text that has been inserted
+ */
+ public String getText() {
+ return fText;
+ }
+
+ /**
+ * Returns the text that has been replaced.
+ *
+ * @return the text that has been replaced
+ */
+ public String getPreservedText() {
+ return fPreservedText;
+ }
+
+ /**
+ * Returns the type of event that is occurring.
+ *
+ * @return the bit mask that indicates the type (or types) of the event
+ */
+ public int getEventType() {
+ return fEventType;
+ }
+
+ /**
+ * Returns the adapter providing additional undo or redo information.
+ *
+ * @return the adapter providing adapters for additional contextual
+ * information about the triggering undo or redo. This adapter
+ * may be <code>null</code>.
+ */
+ public IAdaptable getInfoAdapter() {
+ return fInfoAdapter;
+ }
+
+ /**
+ * Returns whether the change was a compound change or not.
+ *
+ * @return <code>true</code> if the undo or redo change is a
+ * compound change, <code>false</code> if it is not
+ */
+ public boolean isCompound() {
+ return (fEventType & COMPOUND) != 0;
+ }
+}
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java
new file mode 100644
index 000000000..c94c7df7c
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java
@@ -0,0 +1,719 @@
+/*******************************************************************************
+ * 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.commands.operations.IContextReplacingOperation;
+import org.eclipse.core.commands.operations.IOperationHistory;
+import org.eclipse.core.commands.operations.IOperationHistoryListener;
+import org.eclipse.core.commands.operations.IUndoContext;
+import org.eclipse.core.commands.operations.IUndoableOperation;
+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.ListenerList;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentExtension4;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.TextUtilities;
+
+/**
+ * A standard implementation of a document-based undo manager that
+ * creates an undo history based on changes to its document.
+ * <p>
+ * Based on the 3.1 implementation of DefaultUndoManager, it was implemented
+ * using the document-related manipulations defined in the original
+ * DefaultUndoManager, by separating the document manipulations from the
+ * viewer-specific processing.</p>
+ * <p>
+ * The classes representing individual text edits (formerly text commands)
+ * were promoted from inner types to their own classes in order to support
+ * reassignment to a different undo manager.<p>
+ * <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>
+ *
+ * @see IDocumentUndoManager
+ * @see DocumentUndoManagerRegistry
+ * @see IDocumentUndoListener
+ * @see org.eclipse.jface.text.IDocument
+ * @since 3.2
+ */
+public class DocumentUndoManager implements IDocumentUndoManager {
+
+ /**
+ * Internal listener to document changes.
+ */
+ private class DocumentListener implements IDocumentListener {
+
+ private String fReplacedText;
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentListener#documentAboutToBeChanged(org.eclipse.jface.text.DocumentEvent)
+ */
+ public void documentAboutToBeChanged(DocumentEvent event) {
+ try {
+ fReplacedText= event.getDocument().get(event.getOffset(),
+ event.getLength());
+ fPreservedUndoModificationStamp= event.getModificationStamp();
+ } catch (BadLocationException x) {
+ fReplacedText= null;
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentListener#documentChanged(org.eclipse.jface.text.DocumentEvent)
+ */
+ public void documentChanged(DocumentEvent event) {
+ fPreservedRedoModificationStamp= event.getModificationStamp();
+
+ // record the current valid state for the top operation in case it
+ // remains the
+ // top operation but changes state.
+ IUndoableOperation op= fHistory.getUndoOperation(fUndoContext);
+ boolean wasValid= false;
+ if (op != null)
+ wasValid= op.canUndo();
+ // Process the change, providing the before and after timestamps
+ processChange(event.getOffset(), event.getOffset()
+ + event.getLength(), event.getText(), fReplacedText,
+ fPreservedUndoModificationStamp,
+ fPreservedRedoModificationStamp);
+
+ // now update fCurrent with the latest buffers from the document
+ // change.
+ fCurrent.pretendCommit();
+
+ if (op == fCurrent) {
+ // if the document change did not cause a new fCurrent to be
+ // created, then we should
+ // notify the history that the current operation changed if its
+ // validity has changed.
+ if (wasValid != fCurrent.isValid())
+ fHistory.operationChanged(op);
+ } else {
+ // if the change created a new fCurrent that we did not yet add
+ // to the
+ // stack, do so if it's valid and we are not in the middle of a
+ // compound change.
+ if (fCurrent != fLastAddedTextEdit && fCurrent.isValid()) {
+ addToOperationHistory(fCurrent);
+ }
+ }
+ }
+ }
+
+ /*
+ * @see IOperationHistoryListener
+ */
+ class HistoryListener implements IOperationHistoryListener {
+ private IUndoableOperation fOperation;
+
+ public void historyNotification(final OperationHistoryEvent event) {
+ final int type= event.getEventType();
+ switch (type) {
+ case OperationHistoryEvent.ABOUT_TO_UNDO:
+ case OperationHistoryEvent.ABOUT_TO_REDO:
+ // if this is one of our operations
+ if (event.getOperation().hasContext(fUndoContext)) {
+ // if we are undoing/redoing an operation we generated, then
+ // ignore
+ // the document changes associated with this undo or redo.
+ if (event.getOperation() instanceof UndoableTextChange) {
+ listenToTextChanges(false);
+
+ // in the undo case only, make sure compounds are closed
+ if (type == OperationHistoryEvent.ABOUT_TO_UNDO) {
+ if (fFoldingIntoCompoundChange) {
+ endCompoundChange();
+ }
+ }
+ } else {
+ // the undo or redo has our context, but it is not one
+ // of our edits. We will listen to the changes, but will
+ // reset the state that tracks the undo/redo history.
+ commit();
+ fLastAddedTextEdit= null;
+ }
+ fOperation= event.getOperation();
+ }
+ break;
+ case OperationHistoryEvent.UNDONE:
+ case OperationHistoryEvent.REDONE:
+ case OperationHistoryEvent.OPERATION_NOT_OK:
+ if (event.getOperation() == fOperation) {
+ listenToTextChanges(true);
+ fOperation= null;
+ }
+ break;
+ }
+ }
+
+ }
+
+ /**
+ * The undo context for this document undo manager.
+ */
+ ObjectUndoContext fUndoContext;
+
+ /**
+ * The document whose changes are being tracked.
+ */
+ IDocument fDocument;
+
+ /**
+ * The currently constructed edit.
+ */
+ UndoableTextChange fCurrent;
+
+ /**
+ * The internal document listener.
+ */
+ private DocumentListener fDocumentListener;
+
+ /**
+ * Indicates whether the current change belongs to a compound change.
+ */
+ boolean fFoldingIntoCompoundChange= false;
+
+ /**
+ * The operation history being used to store the undo history.
+ */
+ IOperationHistory fHistory;
+
+ /**
+ * The operation history listener used for managing undo and redo before and
+ * after the individual edits are performed.
+ */
+ private IOperationHistoryListener fHistoryListener;
+
+ /**
+ * The text edit last added to the operation history. This must be tracked
+ * internally instead of asking the history, since outside parties may be
+ * placing items on our undo/redo history.
+ */
+ private UndoableTextChange fLastAddedTextEdit= null;
+
+ /**
+ * The document modification stamp for redo.
+ */
+ protected long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
+
+ /**
+ * Text buffer to collect viewer content which has been replaced
+ */
+ StringBuffer fPreservedTextBuffer;
+
+ /**
+ * The document modification stamp for undo.
+ */
+ protected long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
+
+ /**
+ * The last delete text edit.
+ */
+ private UndoableTextChange fPreviousDelete;
+
+ /**
+ * Text buffer to collect text which is inserted into the viewer
+ */
+ StringBuffer fTextBuffer;
+
+ /** Indicates inserting state. */
+ private boolean fInserting= false;
+
+ /** Indicates overwriting state. */
+ private boolean fOverwriting= false;
+
+ /** The registered document listeners. */
+ private ListenerList fDocumentUndoListeners;
+
+ /** The list of clients connected. */
+ private List fConnected;
+
+ /**
+ *
+ * Create a DocumentUndoManager for the given document.
+ *
+ * @param document the document whose undo history is being managed.
+ */
+ public DocumentUndoManager(IDocument document) {
+ super();
+ Assert.isNotNull(document);
+ fDocument= document;
+ fHistory= OperationHistoryFactory.getOperationHistory();
+ fUndoContext= new ObjectUndoContext(fDocument);
+ fConnected= new ArrayList();
+ fDocumentUndoListeners= new ListenerList();
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#addDocumentUndoListener(org.eclipse.jface.text.IDocumentUndoListener)
+ */
+ public void addDocumentUndoListener(IDocumentUndoListener listener) {
+ fDocumentUndoListeners.add(listener);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#removeDocumentUndoListener(org.eclipse.jface.text.IDocumentUndoListener)
+ */
+ public void removeDocumentUndoListener(IDocumentUndoListener listener) {
+ fDocumentUndoListeners.remove(listener);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#getUndoContext()
+ */
+ public IUndoContext getUndoContext() {
+ return fUndoContext;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#commit()
+ */
+ public void commit() {
+
+ fInserting= false;
+ fOverwriting= false;
+ fPreviousDelete.reinitialize();
+
+ // if fCurrent has never been placed on the history, do so now.
+ // this can happen when there are multiple programmatically commits in a
+ // single document change.
+ if (fLastAddedTextEdit != fCurrent) {
+ fCurrent.pretendCommit();
+ if (fCurrent.isValid())
+ addToOperationHistory(fCurrent);
+ }
+ fCurrent.commit();
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#connect(java.lang.Object)
+ */
+ public void connect(Object client) {
+ if (!isConnected()) {
+ initialize();
+ }
+ if (!fConnected.contains(client))
+ fConnected.add(client);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#disconnect(java.lang.Object)
+ */
+ public void disconnect(Object client) {
+ fConnected.remove(client);
+ if (!isConnected()) {
+ shutdown();
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#beginCompoundChange()
+ */
+ public void beginCompoundChange() {
+ if (isConnected()) {
+ fFoldingIntoCompoundChange= true;
+ commit();
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#endCompoundChange()
+ */
+ public void endCompoundChange() {
+ if (isConnected()) {
+ fFoldingIntoCompoundChange= false;
+ commit();
+ }
+ }
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#setUndoLimit(int)
+ */
+ public void setUndoLimit(int undoLimit) {
+ fHistory.setLimit(fUndoContext, undoLimit);
+ }
+
+ /**
+ * Fires a document undo event to all registered document undo listeners.
+ * Uses a robust iterator.
+ *
+ * @param offset the document offset
+ * @param text the text that was inserted
+ * @param preservedText the text being replaced
+ * @param uiInfo an adapter that may provide additional UI info about the triggering action
+ * @param eventType the type of event causing the change
+ * @param isCompound a flag indicating whether the change is a compound change
+ * @see IDocumentUndoListener
+ */
+ void fireDocumentUndo(int offset, String text, String preservedText, IAdaptable uiInfo, int eventType, boolean isCompound) {
+ eventType= isCompound ? eventType | DocumentUndoEvent.COMPOUND : eventType;
+ DocumentUndoEvent event= new DocumentUndoEvent(fDocument, offset, text, preservedText, eventType, uiInfo);
+ Object[] listeners= fDocumentUndoListeners.getListeners();
+ for (int i= 0; i < listeners.length; i++) {
+ ((IDocumentUndoListener)listeners[i]).documentUndoNotification(event);
+ }
+
+ }
+
+ /**
+ * Adds any listeners needed to track the document and the operations
+ * history.
+ */
+ private void addListeners() {
+ fHistoryListener= new HistoryListener();
+ fHistory.addOperationHistoryListener(fHistoryListener);
+ listenToTextChanges(true);
+ }
+
+ /**
+ * Removes any listeners that were installed by the document.
+ */
+ private void removeListeners() {
+ listenToTextChanges(false);
+ fHistory.removeOperationHistoryListener(fHistoryListener);
+ fHistoryListener= null;
+ }
+
+ /**
+ * Adds the given text edit to the operation history if it is not part of a
+ * compound change.
+ *
+ * @param edit
+ * the edit to be added
+ */
+ private void addToOperationHistory(UndoableTextChange edit) {
+ if (!fFoldingIntoCompoundChange
+ || edit instanceof UndoableCompoundTextChange) {
+ fHistory.add(edit);
+ fLastAddedTextEdit= edit;
+ }
+ }
+
+ /**
+ * Disposes the undo history.
+ */
+ private void disposeUndoHistory() {
+ fHistory.dispose(fUndoContext, true, true, true);
+ }
+
+ /**
+ * Initializes the undo history.
+ */
+ private void initializeUndoHistory() {
+ if (fHistory != null && fUndoContext != null)
+ fHistory.dispose(fUndoContext, true, true, false);
+
+ }
+
+ /**
+ * Checks whether the given text starts with a line delimiter and
+ * subsequently contains a white space only.
+ *
+ * @param text the text to check
+ * @return <code>true</code> if the text is a line delimiter followed by
+ * whitespace, <code>false</code> otherwise
+ */
+ private boolean isWhitespaceText(String text) {
+
+ if (text == null || text.length() == 0)
+ return false;
+
+ String[] delimiters= fDocument.getLegalLineDelimiters();
+ int index= TextUtilities.startsWith(delimiters, text);
+ if (index > -1) {
+ char c;
+ int length= text.length();
+ for (int i= delimiters[index].length(); i < length; i++) {
+ c= text.charAt(i);
+ if (c != ' ' && c != '\t')
+ return false;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Switches the state of whether there is a text listener or not.
+ *
+ * @param listen the state which should be established
+ */
+ private void listenToTextChanges(boolean listen) {
+ if (listen) {
+ if (fDocumentListener == null && fDocument != null) {
+ fDocumentListener= new DocumentListener();
+ fDocument.addDocumentListener(fDocumentListener);
+ }
+ } else if (!listen) {
+ if (fDocumentListener != null && fDocument != null) {
+ fDocument.removeDocumentListener(fDocumentListener);
+ fDocumentListener= null;
+ }
+ }
+ }
+
+ private void processChange(int modelStart, int modelEnd,
+ String insertedText, String replacedText,
+ long beforeChangeModificationStamp,
+ long afterChangeModificationStamp) {
+
+ if (insertedText == null)
+ insertedText= ""; //$NON-NLS-1$
+
+ if (replacedText == null)
+ replacedText= ""; //$NON-NLS-1$
+
+ int length= insertedText.length();
+ int diff= modelEnd - modelStart;
+
+ if (fCurrent.fUndoModificationStamp == IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP)
+ fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+ // normalize
+ if (diff < 0) {
+ int tmp= modelEnd;
+ modelEnd= modelStart;
+ modelStart= tmp;
+ }
+
+ if (modelStart == modelEnd) {
+ // text will be inserted
+ if ((length == 1) || isWhitespaceText(insertedText)) {
+ // by typing or whitespace
+ if (!fInserting
+ || (modelStart != fCurrent.fStart
+ + fTextBuffer.length())) {
+ fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
+ if (fCurrent.attemptCommit())
+ fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+ fInserting= true;
+ }
+ if (fCurrent.fStart < 0)
+ fCurrent.fStart= fCurrent.fEnd= modelStart;
+ if (length > 0)
+ fTextBuffer.append(insertedText);
+ } else if (length > 0) {
+ // by pasting or model manipulation
+ fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
+ if (fCurrent.attemptCommit())
+ fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+ fCurrent.fStart= fCurrent.fEnd= modelStart;
+ fTextBuffer.append(insertedText);
+ fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
+ if (fCurrent.attemptCommit())
+ fCurrent.fUndoModificationStamp= afterChangeModificationStamp;
+
+ }
+ } else {
+ if (length == 0) {
+ // text will be deleted by backspace or DEL key or empty
+ // clipboard
+ length= replacedText.length();
+ String[] delimiters= fDocument.getLegalLineDelimiters();
+
+ if ((length == 1)
+ || TextUtilities.equals(delimiters, replacedText) > -1) {
+
+ // whereby selection is empty
+
+ if (fPreviousDelete.fStart == modelStart
+ && fPreviousDelete.fEnd == modelEnd) {
+ // repeated DEL
+
+ // correct wrong settings of fCurrent
+ if (fCurrent.fStart == modelEnd
+ && fCurrent.fEnd == modelStart) {
+ fCurrent.fStart= modelStart;
+ fCurrent.fEnd= modelEnd;
+ }
+ // append to buffer && extend edit range
+ fPreservedTextBuffer.append(replacedText);
+ ++fCurrent.fEnd;
+
+ } else if (fPreviousDelete.fStart == modelEnd) {
+ // repeated backspace
+
+ // insert in buffer and extend edit range
+ fPreservedTextBuffer.insert(0, replacedText);
+ fCurrent.fStart= modelStart;
+
+ } else {
+ // either DEL or backspace for the first time
+
+ fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
+ if (fCurrent.attemptCommit())
+ fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+ // as we can not decide whether it was DEL or backspace
+ // we initialize for backspace
+ fPreservedTextBuffer.append(replacedText);
+ fCurrent.fStart= modelStart;
+ fCurrent.fEnd= modelEnd;
+ }
+
+ fPreviousDelete.set(modelStart, modelEnd);
+
+ } else if (length > 0) {
+ // whereby selection is not empty
+ fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
+ if (fCurrent.attemptCommit())
+ fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+ fCurrent.fStart= modelStart;
+ fCurrent.fEnd= modelEnd;
+ fPreservedTextBuffer.append(replacedText);
+ }
+ } else {
+ // text will be replaced
+
+ if (length == 1) {
+ length= replacedText.length();
+ String[] delimiters= fDocument.getLegalLineDelimiters();
+
+ if ((length == 1)
+ || TextUtilities.equals(delimiters, replacedText) > -1) {
+ // because of overwrite mode or model manipulation
+ if (!fOverwriting
+ || (modelStart != fCurrent.fStart
+ + fTextBuffer.length())) {
+ fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
+ if (fCurrent.attemptCommit())
+ fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+ fOverwriting= true;
+ }
+
+ if (fCurrent.fStart < 0)
+ fCurrent.fStart= modelStart;
+
+ fCurrent.fEnd= modelEnd;
+ fTextBuffer.append(insertedText);
+ fPreservedTextBuffer.append(replacedText);
+ fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
+ return;
+ }
+ }
+ // because of typing or pasting whereby selection is not empty
+ fCurrent.fRedoModificationStamp= beforeChangeModificationStamp;
+ if (fCurrent.attemptCommit())
+ fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+ fCurrent.fStart= modelStart;
+ fCurrent.fEnd= modelEnd;
+ fTextBuffer.append(insertedText);
+ fPreservedTextBuffer.append(replacedText);
+ }
+ }
+ // in all cases, the redo modification stamp is updated on the open
+ // text edit
+ fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
+ }
+
+ /**
+ * Initialize the receiver.
+ */
+ private void initialize() {
+ initializeUndoHistory();
+
+ // open up the current text edit
+ fCurrent= new UndoableTextChange(this);
+ fPreviousDelete= new UndoableTextChange(this);
+ fTextBuffer= new StringBuffer();
+ fPreservedTextBuffer= new StringBuffer();
+
+ addListeners();
+ }
+
+ /**
+ * Shutdown the receiver.
+ */
+ private void shutdown() {
+ removeListeners();
+
+ fCurrent= null;
+ fPreviousDelete= null;
+ fTextBuffer= null;
+ fPreservedTextBuffer= null;
+
+ disposeUndoHistory();
+ }
+
+ /**
+ * Return whether or not any clients are connected to the receiver.
+ *
+ * @return <code>true</code> if the receiver is connected to
+ * clients, <code>false</code> if it is not
+ */
+ boolean isConnected() {
+ if (fConnected == null)
+ return false;
+ return !fConnected.isEmpty();
+ }
+
+
+ /*
+ * @see org.eclipse.jface.text.IDocumentUndoManager#transferUndoHistory(IDocumentUndoManager)
+ */
+ public void transferUndoHistory(IDocumentUndoManager manager) {
+ IUndoContext oldUndoContext= manager.getUndoContext();
+ // Get the history for the old undo context.
+ IUndoableOperation [] operations= OperationHistoryFactory.getOperationHistory().getUndoHistory(oldUndoContext);
+ for (int i= 0; i< operations.length; i++) {
+ // First replace the undo context
+ IUndoableOperation op= operations[i];
+ if (op instanceof IContextReplacingOperation) {
+ ((IContextReplacingOperation)op).replaceContext(oldUndoContext, getUndoContext());
+ } else {
+ op.addContext(getUndoContext());
+ op.removeContext(oldUndoContext);
+ }
+ // Now update the manager that owns the text edit.
+ if (op instanceof UndoableTextChange) {
+ ((UndoableTextChange)op).manager= this;
+ }
+ }
+
+ // Record the transfer itself as an undoable change.
+ // If the transfer results from some open operation, recording this change will
+ // cause our undo context to be added to the outer operation. If there is no
+ // outer operation, there will be a local change to signify the transfer.
+ // This also serves to synchronize the modification stamps with the documents.
+ IUndoableOperation op= OperationHistoryFactory.getOperationHistory().getUndoOperation(getUndoContext());
+ UndoableTextChange cmd= new UndoableTextChange(this);
+ cmd.fStart= cmd.fEnd= 0;
+ cmd.fText= cmd.fPreservedText= ""; //$NON-NLS-1$
+ if (fDocument instanceof IDocumentExtension4) {
+ cmd.fRedoModificationStamp= ((IDocumentExtension4)fDocument).getModificationStamp();
+ if (op instanceof UndoableTextChange) {
+ cmd.fUndoModificationStamp= ((UndoableTextChange)op).fRedoModificationStamp;
+ }
+ }
+ addToOperationHistory(cmd);
+ }
+
+}
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java
new file mode 100644
index 000000000..404eb3c73
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 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.HashMap;
+import java.util.Map;
+
+import org.eclipse.jface.text.Assert;
+import org.eclipse.jface.text.IDocument;
+
+/**
+ * This document undo manager registry provides access to a document's
+ * undo manager. In order to connect a document a document undo manager
+ * call <code>connect</code>. After that call has successfully completed
+ * undo manager can be obtained via <code>getDocumentUndoManager</code>.
+ * The undo manager is created on the first connect and disposed on the last
+ * disconnect, i.e. this registry keeps track of how often a undo manager is
+ * connected and returns the same undo manager to each client as long as the
+ * document is connected.
+ * <p>
+ * <em>The recoding of changes starts with the first {@link #connect(IDocument)}.</em></p>
+ * <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 {
+ public Record(IDocument document) {
+ count= 0;
+ undoManager= new DocumentUndoManager(document);
+ }
+ private int count;
+ private IDocumentUndoManager undoManager;
+ }
+
+ private static Map fgFactory= new HashMap();
+
+ private DocumentUndoManagerRegistry() {
+ // Do not instantiate
+ }
+
+
+ /**
+ * Connects the file at the given location to this manager. After that call
+ * successfully completed it is guaranteed that each call to <code>getFileBuffer</code>
+ * returns the same file buffer until <code>disconnect</code> is called.
+ * <p>
+ * <em>The recoding of changes starts with the first {@link #connect(IDocument)}.</em></p>
+ *
+ * @param document the document to be connected
+ */
+ public static synchronized void connect(IDocument document) {
+ Assert.isNotNull(document);
+ Record record= (Record)fgFactory.get(document);
+ if (record == null) {
+ record= new Record(document);
+ fgFactory.put(document, record);
+ }
+ record.count++;
+ }
+
+ /**
+ * Disconnects the given document from this registry.
+ *
+ * @param document the document to be disconnected
+ */
+ public static synchronized void disconnect(IDocument document) {
+ Assert.isNotNull(document);
+ Record record= (Record)fgFactory.get(document);
+ record.count--;
+ if (record.count == 0)
+ fgFactory.remove(document);
+
+ }
+
+ /**
+ * Returns the file buffer managed for the given location or <code>null</code>
+ * if there is no such file buffer.
+ * <p>
+ * The provided location is either a full path of a workspace resource or
+ * an absolute path in the local file system. The file buffer manager does
+ * not resolve the location of workspace resources in the case of linked
+ * resources.
+ * </p>
+ *
+ * @param document the document for which to get its undo manager
+ * @return the document undo manager or <code>null</code>
+ */
+ public static synchronized IDocumentUndoManager getDocumentUndoManager(IDocument document) {
+ Assert.isNotNull(document);
+ Record record= (Record)fgFactory.get(document);
+ if (record == null)
+ return null;
+ return record.undoManager;
+ }
+
+}
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoListener.java b/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoListener.java
new file mode 100644
index 000000000..2406acdd9
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoListener.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * 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;
+
+
+/**
+ * This interface is used to listen to notifications from a DocumentUndoManager.
+ * The supplied DocumentUndoEvent describes the particular notification.
+ * <p>
+ * Document undo listeners must be prepared to receive notifications from a
+ * background thread. Any UI access occurring inside the implementation must be
+ * properly synchronized using the techniques specified by the client's widget
+ * library.</p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * <p>
+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen.
+ * </p>
+ *
+ * @since 3.2
+ */
+public interface IDocumentUndoListener {
+
+ /**
+ * The document is involved in an undo-related change. Notify listeners
+ * with an event describing the change.
+ *
+ * @param event the document undo event that describes the particular notification
+ */
+ void documentUndoNotification(DocumentUndoEvent event);
+
+}
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java b/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java
new file mode 100644
index 000000000..080d36197
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java
@@ -0,0 +1,139 @@
+/*******************************************************************************
+ * 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.IUndoContext;
+
+/**
+ * Interface for a document undo manager. Tracks changes in a document and
+ * builds a history of text commands that describe the undoable changes to the
+ * document.
+ * <p>
+ * Clients must explicitly connect to the undo manager to express their interest
+ * in the undo history. Clients should disconnect from the undo manager when
+ * they are no longer interested in tracking the undo history. If there are no
+ * clients connected to the undo manager, it will not track the document's
+ * changes and will dispose of any history that was previously kept.</p>
+ * <p>
+ * Clients may also listen to the undo manager for notifications before and
+ * after undo or redo events are performed. Clients must connect to the undo
+ * manager in addition to registering listeners.</p>
+ * <p>
+ * Clients may implement this interface.
+ * </p>
+ * <p>
+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen.
+ * </p>
+ *
+ * @see DocumentUndoManagerRegistry
+ * @see IDocumentUndoListener
+ * @see UndoableTextChange
+ * @see UndoableCompoundTextChange
+ * @see org.eclipse.jface.text.IDocument
+ * @since 3.2
+ */
+public interface IDocumentUndoManager {
+
+ /**
+ * Adds the specified listener to the list of document undo listeners that
+ * are notified before and after changes are undone or redone in the
+ * document. This method has no effect if the instance being added is
+ * already in the list.
+ * <p>
+ * Notifications will not be received if there are no clients connected to
+ * the receiver. Registering a document undo listener does not implicitly
+ * connect the listener to the receiver.</p>
+ * <p>
+ * Document undo listeners must be prepared to receive notifications from a
+ * background thread. Any UI access occurring inside the implementation must
+ * be properly synchronized using the techniques specified by the client's
+ * widget library.</p>
+ *
+ * @param listener the document undo listener to be added as a listener
+ */
+ public abstract void addDocumentUndoListener(IDocumentUndoListener listener);
+
+ /**
+ * Removes the specified listener from the list of document undo listeners.
+ * <p>
+ * Removing a listener which is not registered has no effect
+ * </p>
+ *
+ * @param listener the document undo listener to be removed
+ */
+ public abstract void removeDocumentUndoListener(IDocumentUndoListener listener);
+
+ /**
+ * Returns the undo context registered for this document
+ *
+ * @return the undo context registered for this document
+ */
+ public abstract IUndoContext getUndoContext();
+
+ /**
+ * Closes the currently open text edit and open a new one.
+ */
+ public abstract void commit();
+
+ /**
+ * Connects to the undo manager. Used to signify that a client is monitoring
+ * the history kept by the undo manager. This message has no effect if the
+ * client is already connected.
+ *
+ * @param client the object connecting to the undo manager
+ */
+ public abstract void connect(Object client);
+
+ /**
+ * Disconnects from the undo manager. Used to signify that a client is no
+ * longer monitoring the history kept by the undo manager. If all clients
+ * have disconnected from the undo manager, the undo history will be
+ * deleted.
+ *
+ * @param client the object disconnecting from the undo manager
+ */
+ public abstract 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();
+
+ /**
+ * 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();
+
+ /**
+ * Sets the limit of the undo history to the specified value. The provided
+ * limit will supersede any previously set limit.
+ *
+ * @param undoLimit the length of this undo manager's history
+ */
+ public abstract void setUndoLimit(int undoLimit);
+
+ /**
+ * Transfers the undo history from the specified document undo manager to
+ * this undo manager. This message should only be used when it is known
+ * that the content of the document of the original undo manager when the
+ * last undo operation was recorded is the same as this undo manager's
+ * current document content, since the undo history is based on document
+ * indexes. It is the responsibility of the caller
+ * to ensure that this call is used correctly.
+ *
+ * @param manager the document undo manger whose history is to be transferred to the receiver
+ */
+ public void transferUndoHistory(IDocumentUndoManager manager);
+
+}
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.java b/org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.java
new file mode 100644
index 000000000..6c931382d
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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.text.MessageFormat;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+/**
+ * Helper class to get NLSed messages.
+ *
+ * @since 3.2
+ */
+final class UndoMessages {
+
+ private static final String BUNDLE_NAME= "org.eclipse.text.undo.UndoMessages"; //$NON-NLS-1$
+
+ private static final ResourceBundle RESOURCE_BUNDLE= ResourceBundle.getBundle(BUNDLE_NAME);
+
+ private UndoMessages() {
+ }
+
+ public static String getString(String key) {
+ try {
+ return RESOURCE_BUNDLE.getString(key);
+ } catch (MissingResourceException e) {
+ return '!' + key + '!';
+ }
+ }
+
+ public static String getFormattedString(String key, Object arg) {
+ return getFormattedString(key, new Object[] { arg });
+ }
+
+ public static String getFormattedString(String key, Object[] args) {
+ return MessageFormat.format(getString(key), args);
+ }
+}
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.properties b/org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.properties
new file mode 100644
index 000000000..1f2d887d8
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.properties
@@ -0,0 +1,12 @@
+###############################################################################
+# 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
+###############################################################################
+
+DocumentUndoManager.operationLabel= Typing
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java b/org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java
new file mode 100644
index 000000000..65449e0f1
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java
@@ -0,0 +1,181 @@
+/*******************************************************************************
+ * 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.jface.text.UndoableTextChange#undo()
+ */
+ 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.jface.text.UndoableTextChange#redo()
+ */
+ 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 UndoableTextChange#updateUndoableTextChange
+ */
+ 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 UndoableTextChange#createCurrent
+ */
+ protected UndoableTextChange createCurrent() {
+
+ if (!manager.fFoldingIntoCompoundChange)
+ return new UndoableTextChange(manager);
+
+ reinitialize();
+ return this;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.UndoableTextChange#commit()
+ */
+ protected void commit() {
+ // if there is pending data, update the text change
+ if (fStart > -1)
+ updateTextChange();
+ manager.fCurrent= createCurrent();
+ }
+
+ /**
+ * Checks whether the text change is valid for undo or redo.
+ *
+ * @return true if the text change is valid
+ */
+ protected boolean isValid() {
+ return fStart > -1 || fChanges.size() > 0;
+ }
+
+ /**
+ * Returns the undo modification stamp.
+ *
+ * @return the undo modification stamp
+ */
+ protected long getUndoModificationStamp() {
+ if (fStart > -1)
+ return super.getUndoModificationStamp();
+ else if (fChanges.size() > 0)
+ return ((UndoableTextChange) fChanges.get(0))
+ .getUndoModificationStamp();
+
+ return fUndoModificationStamp;
+ }
+
+ /**
+ * Returns the redo modification stamp.
+ *
+ * @return the redo modification stamp
+ */
+ 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
new file mode 100644
index 000000000..e5ddbf409
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/UndoableTextChange.java
@@ -0,0 +1,384 @@
+/*******************************************************************************
+ * 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#execute(org.eclipse.core.runtime.IProgressMonitor,
+ * org.eclipse.core.runtime.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() {
+ return manager.fFoldingIntoCompoundChange ? new UndoableCompoundTextChange(
+ manager) : 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 fCurrent, <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;
+ }
+}
diff --git a/org.eclipse.text/src/org/eclipse/text/undo/package.html b/org.eclipse.text/src/org/eclipse/text/undo/package.html
new file mode 100644
index 000000000..2bf670956
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/package.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//w3c//dtd html 4.0 transitional//en">
+<html><head>
+ <meta content="text/html; charset=iso-8859-1" http-equiv="Content-Type">
+ <meta content="IBM" name="Author">
+ <meta content="Mozilla/4.75 [en] (Windows NT 5.0; U) [Netscape]" name="GENERATOR"><title>Document Undo Support</title></head>
+
+<body>
+<p>Provides undo and redo support for a document.</p>
+<h3>Package Specification</h3>
+<p>When an <tt>IDocumentUndoManager</tt> is connected to an <tt>IDocument</tt>,
+ any change to the document is recorded and can then be undone and redone later.
+ Clients which are interested in undo/redo events can register an <tt>IDocumentUndoistener</tt>
+ with the <tt>IDocumentUndoManager</tt>.</p>
+</body></html> \ No newline at end of file

Back to the top