From 9e0a0e4bf90c5b897ff8ec6f3cdcd98287308bc7 Mon Sep 17 00:00:00 2001 From: Dani Megert Date: Tue, 24 Jan 2006 10:29:21 +0000 Subject: First cut of fix for bug 89599: [api][typing] Text Editor Undo stack (context) should be keyed of common document --- .../org/eclipse/text/undo/DocumentUndoEvent.java | 168 +++++ .../org/eclipse/text/undo/DocumentUndoManager.java | 719 +++++++++++++++++++++ .../text/undo/DocumentUndoManagerRegistry.java | 111 ++++ .../eclipse/text/undo/IDocumentUndoListener.java | 41 ++ .../eclipse/text/undo/IDocumentUndoManager.java | 139 ++++ .../src/org/eclipse/text/undo/UndoMessages.java | 46 ++ .../org/eclipse/text/undo/UndoMessages.properties | 12 + .../text/undo/UndoableCompoundTextChange.java | 181 ++++++ .../org/eclipse/text/undo/UndoableTextChange.java | 384 +++++++++++ .../src/org/eclipse/text/undo/package.html | 14 + 10 files changed, 1815 insertions(+) create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoEvent.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManager.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/DocumentUndoManagerRegistry.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoListener.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/IDocumentUndoManager.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/UndoMessages.properties create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/UndoableCompoundTextChange.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/UndoableTextChange.java create mode 100644 org.eclipse.text/src/org/eclipse/text/undo/package.html (limited to 'org.eclipse.text/src/org/eclipse/text/undo') 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 00000000000..3b13af2a7e8 --- /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. + *
+ * Clients are not supposed to subclass or create instances of this class. + *
+ *+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen. + *
+ * + * @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 ornull
+ */
+ 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 null
.
+ */
+ public IAdaptable getInfoAdapter() {
+ return fInfoAdapter;
+ }
+
+ /**
+ * Returns whether the change was a compound change or not.
+ *
+ * @return true
if the undo or redo change is a
+ * compound change, false
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 00000000000..c94c7df7cb7
--- /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.
+ * + * 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.
+ *+ * 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.
+ *
+ * This class is not intended to be subclassed. + *
+ *+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen. + *
+ * + * @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 + * @returntrue
if the text is a line delimiter followed by
+ * whitespace, false
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 true
if the receiver is connected to
+ * clients, false
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 00000000000..404eb3c7372
--- /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 connect
. After that call has successfully completed
+ * undo manager can be obtained via getDocumentUndoManager
.
+ * 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.
+ * + * The recoding of changes starts with the first {@link #connect(IDocument)}.
+ *+ * This class is not intended to be subclassed. + *
+ *+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen. + *
+ * + * @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 togetFileBuffer
+ * returns the same file buffer until disconnect
is called.
+ * + * The recoding of changes starts with the first {@link #connect(IDocument)}.
+ * + * @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 ornull
+ * if there is no such file buffer.
+ * + * 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. + *
+ * + * @param document the document for which to get its undo manager + * @return the document undo manager ornull
+ */
+ 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 00000000000..2406acdd9ad
--- /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.
+ * + * 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.
+ *+ * Clients may implement this interface. + *
+ *+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen. + *
+ * + * @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 00000000000..080d3619763 --- /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. + *+ * 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.
+ *+ * 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.
+ *+ * Clients may implement this interface. + *
+ *+ * XXX: This is work in progress and can change anytime until API for 3.2 is frozen. + *
+ * + * @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. + *+ * 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.
+ *+ * 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.
+ * + * @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. + *+ * Removing a listener which is not registered has no effect + *
+ * + * @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 + *endCompoundChange
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
+ * beginCompoundChange
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 00000000000..6c931382d90
--- /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 00000000000..1f2d887d863
--- /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 00000000000..65449e0f138
--- /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 00000000000..e5ddbf409fc
--- /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.
+ * + * Based on the DefaultUndoManager.TextCommand from R3.1. + *
+ * + * @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. + * + * @returntrue
if the change was committed and created
+ * a new fCurrent, false
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 true
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 00000000000..2bf670956db
--- /dev/null
+++ b/org.eclipse.text/src/org/eclipse/text/undo/package.html
@@ -0,0 +1,14 @@
+
+
+
+
+ Provides undo and redo support for a document.
+When an IDocumentUndoManager is connected to an IDocument, + 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 IDocumentUndoistener + with the IDocumentUndoManager.
+ \ No newline at end of file -- cgit v1.2.3