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 or null + */ + 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 + * @return true 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 to getFileBuffer + * 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 or null + * 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 or null + */ + 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. + * + * @return true 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 @@ + + + + + Document Undo Support + + +

Provides undo and redo support for a document.

+

Package Specification

+

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