diff dwtx/jface/text/DefaultUndoManager.d @ 129:eb30df5ca28b

Added JFace Text sources
author Frank Benoit <benoit@tionex.de>
date Sat, 23 Aug 2008 19:10:48 +0200
parents
children c4fb132a086c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dwtx/jface/text/DefaultUndoManager.d	Sat Aug 23 19:10:48 2008 +0200
@@ -0,0 +1,1337 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 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
+ * Port to the D programming language:
+ *     Frank Benoit <benoit@tionex.de>
+ *******************************************************************************/
+
+module dwtx.jface.text.DefaultUndoManager;
+
+import dwt.dwthelper.utils;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import dwt.DWT;
+import dwt.custom.StyledText;
+import dwt.events.KeyEvent;
+import dwt.events.KeyListener;
+import dwt.events.MouseEvent;
+import dwt.events.MouseListener;
+import dwt.widgets.Display;
+import dwt.widgets.Shell;
+import dwtx.core.commands.ExecutionException;
+import dwtx.core.commands.operations.AbstractOperation;
+import dwtx.core.commands.operations.IOperationHistory;
+import dwtx.core.commands.operations.IOperationHistoryListener;
+import dwtx.core.commands.operations.IUndoContext;
+import dwtx.core.commands.operations.IUndoableOperation;
+import dwtx.core.commands.operations.ObjectUndoContext;
+import dwtx.core.commands.operations.OperationHistoryEvent;
+import dwtx.core.commands.operations.OperationHistoryFactory;
+import dwtx.core.runtime.IAdaptable;
+import dwtx.core.runtime.IProgressMonitor;
+import dwtx.core.runtime.IStatus;
+import dwtx.core.runtime.Status;
+import dwtx.jface.dialogs.MessageDialog;
+
+
+/**
+ * Standard implementation of {@link dwtx.jface.text.IUndoManager}.
+ * <p>
+ * It registers with the connected text viewer as text input listener and
+ * document listener and logs all changes. It also monitors mouse and keyboard
+ * activities in order to partition the stream of text changes into undo-able
+ * edit commands.
+ * </p>
+ * <p>
+ * Since 3.1 this undo manager is a facade to the global operation history.
+ * </p>
+ * <p>
+ * The usage of {@link dwtx.core.runtime.IAdaptable} in the JFace
+ * layer has been approved by Platform UI, see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=87669#c9
+ * </p>
+ * <p>
+ * This class is not intended to be subclassed.
+ * </p>
+ *
+ * @see dwtx.jface.text.ITextViewer
+ * @see dwtx.jface.text.ITextInputListener
+ * @see dwtx.jface.text.IDocumentListener
+ * @see dwtx.core.commands.operations.IUndoableOperation
+ * @see dwtx.core.commands.operations.IOperationHistory
+ * @see MouseListener
+ * @see KeyListener
+ * @deprecated As of 3.2, replaced by {@link TextViewerUndoManager}
+ * @noextend This class is not intended to be subclassed by clients.
+ */
+public class DefaultUndoManager : IUndoManager, IUndoManagerExtension {
+
+    /**
+     * Represents an undo-able edit command.
+     * <p>
+     * Since 3.1 this implements the interface for IUndoableOperation.
+     * </p>
+     */
+    class TextCommand : 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;
+
+        /**
+         * Creates a new text command.
+         *
+         * @param context the undo context for this command
+         * @since 3.1
+         */
+        TextCommand(IUndoContext context) {
+            super(JFaceTextMessages.getString("DefaultUndoManager.operationLabel")); //$NON-NLS-1$
+            addContext(context);
+        }
+
+        /**
+         * Re-initializes this text command.
+         */
+        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 command.
+         *
+         * @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 dwtx.core.commands.operations.IUndoableOperation#dispose()
+         * @since 3.1
+         */
+        public void dispose() {
+            reinitialize();
+        }
+
+        /**
+         * Undo the change described by this command.
+         *
+         * @since 2.0
+         */
+        protected void undoTextChange() {
+            try {
+                IDocument document= fTextViewer.getDocument();
+                if (document instanceof IDocumentExtension4)
+                    ((IDocumentExtension4)document).replace(fStart, fText.length(), fPreservedText, fUndoModificationStamp);
+                else
+                    document.replace(fStart, fText.length(), fPreservedText);
+            } catch (BadLocationException x) {
+            }
+        }
+
+        /*
+         * @see dwtx.core.commands.operations.IUndoableOperation#canUndo()
+         * @since 3.1
+         */
+        public bool canUndo() {
+            
+            if (isConnected() && isValid()) {
+                IDocument doc= fTextViewer.getDocument();
+                if (doc instanceof IDocumentExtension4) {
+                    long docStamp= ((IDocumentExtension4)doc).getModificationStamp();
+                    
+                    // Normal case: an undo is valid if its redo will restore document
+                    // to its current modification stamp
+                    bool canUndo= docStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP || 
+                        docStamp is 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 is fHistory.getUndoOperation(fUndoContext)  &&  // this is the latest operation
+                            this !is fCurrent && // there is a more current operation not on the stack
+                            !fCurrent.isValid() &&  // the current operation is not a valid document modification
+                            fCurrent.fUndoModificationStamp !is // the invalid current operation has a document stamp
+                                IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) {
+                        canUndo= fCurrent.fRedoModificationStamp is docStamp;                           
+                    }
+                    /*
+                     * When the composite is the current command, it may hold the timestamp 
+                     * of a no-op change.  We check this here rather than in an override of 
+                     * canUndo() in CompoundTextCommand simply to keep all the special case checks
+                     * in one place.
+                     */ 
+                    if (!canUndo &&
+                            this is fHistory.getUndoOperation(fUndoContext)  &&  // this is the latest operation
+                            this instanceof CompoundTextCommand &&
+                            this is fCurrent && // this is the current operation
+                            this.fStart is -1 &&  // the current operation text is not valid
+                            fCurrent.fRedoModificationStamp !is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) {  // but it has a redo stamp
+                        canUndo= fCurrent.fRedoModificationStamp is docStamp;
+                    }
+
+                }
+                // if there is no timestamp to check, simply return true per the 3.0.1 behavior
+                return true;
+            }
+            return false;
+        }
+
+        /*
+         * @see dwtx.core.commands.operations.IUndoableOperation#canRedo()
+         * @since 3.1
+         */
+        public bool canRedo() {
+            if (isConnected() && isValid()) {
+                IDocument doc= fTextViewer.getDocument();
+                if (doc instanceof IDocumentExtension4) {
+                    long docStamp= ((IDocumentExtension4)doc).getModificationStamp();
+                    return docStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP || 
+                        docStamp is getUndoModificationStamp();
+                }
+                // if there is no timestamp to check, simply return true per the 3.0.1 behavior
+                return true;
+            }
+            return false;
+        }
+
+        /*
+         * @see dwtx.core.commands.operations.IUndoableOperation#canExecute()
+         * @since 3.1
+         */
+        public bool canExecute() {
+            return isConnected();
+        }
+
+        /*
+         * @see dwtx.core.commands.operations.IUndoableOperation#execute(dwtx.core.runtime.IProgressMonitor, dwtx.core.runtime.IAdaptable)
+         * @since 3.1
+         */
+        public IStatus execute(IProgressMonitor monitor, IAdaptable uiInfo) {
+            // Text commands execute as they are typed, so executing one has no effect.
+            return Status.OK_STATUS;
+        }
+
+        /*
+         * Undo the change described by this command. Also selects and
+         * reveals the change.
+         */
+
+        /**
+         * Undo the change described by this command. Also selects and
+         * reveals the change.
+         *
+         * @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 undo(IProgressMonitor monitor, IAdaptable uiInfo) {
+            if (isValid()) {
+                undoTextChange();
+                selectAndReveal(fStart, fPreservedText is null ? 0 : fPreservedText.length());
+                resetProcessChangeSate();
+                return Status.OK_STATUS;
+            }
+            return IOperationHistory.OPERATION_INVALID_STATUS;
+        }
+        
+        /**
+         * Re-applies the change described by this command.
+         *
+         * @since 2.0
+         */
+        protected void redoTextChange() {
+            try {
+                IDocument document= fTextViewer.getDocument();
+                if (document instanceof IDocumentExtension4)
+                    ((IDocumentExtension4)document).replace(fStart, fEnd - fStart, fText, fRedoModificationStamp);
+                else
+                    fTextViewer.getDocument().replace(fStart, fEnd - fStart, fText);
+            } catch (BadLocationException x) {
+            }
+        }
+
+        /**
+         * Re-applies the change described by this command that previously been
+         * rolled back. Also selects and reveals the change.
+         *
+         * @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()) {
+                redoTextChange();
+                resetProcessChangeSate();
+                selectAndReveal(fStart, fText is null ? 0 : fText.length());
+                return Status.OK_STATUS;
+            }
+            return IOperationHistory.OPERATION_INVALID_STATUS;
+        }
+
+        /**
+         * Update the command in response to a commit.
+         *
+         * @since 3.1
+         */
+
+        protected void updateCommand() {
+            fText= fTextBuffer.toString();
+            fTextBuffer.setLength(0);
+            fPreservedText= fPreservedTextBuffer.toString();
+            fPreservedTextBuffer.setLength(0);
+        }
+
+        /**
+         * Creates a new uncommitted text command depending on whether
+         * a compound change is currently being executed.
+         *
+         * @return a new, uncommitted text command or a compound text command
+         */
+        protected TextCommand createCurrent() {
+            return fFoldingIntoCompoundChange ? new CompoundTextCommand(fUndoContext) : new TextCommand(fUndoContext);
+        }
+
+        /**
+         * Commits the current change into this command.
+         */
+        protected void commit() {
+            if (fStart < 0) {
+                if (fFoldingIntoCompoundChange) {
+                    fCurrent= createCurrent();
+                } else {
+                    reinitialize();
+                }
+            } else {
+                updateCommand();
+                fCurrent= createCurrent();
+            }
+            resetProcessChangeSate();
+        }
+
+        /**
+         * Updates the text from the buffers without resetting
+         * the buffers or adding anything to the stack.
+         *
+         * @since 3.1
+         */
+        protected void pretendCommit() {
+            if (fStart > -1) {
+                fText= fTextBuffer.toString();
+                fPreservedText= fPreservedTextBuffer.toString();
+            }
+        }
+        
+        /**
+         * Attempt a commit of this command and answer true if a new
+         * fCurrent was created as a result of the commit.
+         * 
+         * @return true if the command was committed and created a
+         * new fCurrent, false if not.
+         * @since 3.1
+         */
+        protected bool attemptCommit() {
+            pretendCommit();
+            if (isValid()) {
+                DefaultUndoManager.this.commit();
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Checks whether this text command is valid for undo or redo.
+         *
+         * @return <code>true</code> if the command is valid for undo or redo
+         * @since 3.1
+         */
+        protected bool isValid() {
+            return fStart > -1 &&
+                fEnd > -1 &&
+                fText !is null;
+        }
+
+        /*
+         * @see java.lang.Object#toString()
+         * @since 3.1
+         */
+        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 command
+         * @since 3.1
+         */
+        protected long getUndoModificationStamp() {
+            return fUndoModificationStamp;
+        }
+        
+        /**
+         * Return the redo modification stamp
+         * 
+         * @return the redo modification stamp for this command
+         * @since 3.1
+         */
+        protected long getRedoModificationStamp() {
+            return fRedoModificationStamp;
+        }
+    }
+
+    /**
+     * Represents an undo-able edit command consisting of several
+     * individual edit commands.
+     */
+    class CompoundTextCommand : TextCommand {
+
+        /** The list of individual commands */
+        private List fCommands= new ArrayList();
+
+        /**
+         * Creates a new compound text command.
+         *
+         * @param context the undo context for this command
+         * @since 3.1
+         */
+        CompoundTextCommand(IUndoContext context) {
+            super(context);
+        }
+
+        /**
+         * Adds a new individual command to this compound command.
+         *
+         * @param command the command to be added
+         */
+        protected void add(TextCommand command) {
+            fCommands.add(command);
+        }
+
+        /*
+         * @see dwtx.jface.text.DefaultUndoManager.TextCommand#undo()
+         */
+        public IStatus undo(IProgressMonitor monitor, IAdaptable uiInfo) {
+            resetProcessChangeSate();
+            
+            int size= fCommands.size();
+            if (size > 0) {
+
+                TextCommand c;
+
+                for (int i= size -1; i > 0;  --i) {
+                    c= (TextCommand) fCommands.get(i);
+                    c.undoTextChange();
+                }
+
+                c= (TextCommand) fCommands.get(0);
+                c.undo(monitor, uiInfo);
+            }
+
+            return Status.OK_STATUS;
+        }
+
+        /*
+         * @see dwtx.jface.text.DefaultUndoManager.TextCommand#redo()
+         */
+        public IStatus redo(IProgressMonitor monitor, IAdaptable uiInfo) {
+            resetProcessChangeSate();
+
+            int size= fCommands.size();
+            if (size > 0) {
+
+                TextCommand c;
+
+                for (int i= 0; i < size -1;  ++i) {
+                    c= (TextCommand) fCommands.get(i);
+                    c.redoTextChange();
+                }
+
+                c= (TextCommand) fCommands.get(size -1);
+                c.redo(monitor, uiInfo);
+            }
+            return Status.OK_STATUS;
+        }
+
+        /*
+         * @see TextCommand#updateCommand
+
+         */
+
+        protected void updateCommand() {
+            // first gather the data from the buffers
+            super.updateCommand();
+
+            // the result of the command update is stored as a child command
+            TextCommand c= new TextCommand(fUndoContext);
+            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 TextCommand#createCurrent
+         */
+        protected TextCommand createCurrent() {
+
+            if (!fFoldingIntoCompoundChange)
+                return new TextCommand(fUndoContext);
+
+            reinitialize();
+            return this;
+        }
+
+        /*
+         * @see dwtx.jface.text.DefaultUndoManager.TextCommand#commit()
+         */
+        protected void commit() {
+            // if there is pending data, update the command
+            if (fStart > -1)
+                updateCommand();
+            fCurrent= createCurrent();
+            resetProcessChangeSate();
+        }
+
+        /**
+         * Checks whether the command is valid for undo or redo.
+         *
+         * @return true if the command is valid.
+         * @since 3.1
+         */
+        protected bool isValid() {
+            if (isConnected())
+                return (fStart > -1 || fCommands.size() > 0);
+            return false;
+        }
+        
+        /**
+         * Returns the undo modification stamp.
+         * 
+         * @return the undo modification stamp
+         * @since 3.1
+         */
+        protected long getUndoModificationStamp() {
+            if (fStart > -1)
+                return super.getUndoModificationStamp();
+            else if (fCommands.size() > 0)
+                return ((TextCommand)fCommands.get(0)).getUndoModificationStamp();
+
+            return fUndoModificationStamp;
+        }
+        
+        /**
+         * Returns the redo modification stamp.
+         * 
+         * @return the redo modification stamp
+         * @since 3.1
+         */
+        protected long getRedoModificationStamp() {
+            if (fStart > -1)
+                return super.getRedoModificationStamp();
+            else if (fCommands.size() > 0)
+                return ((TextCommand)fCommands.get(fCommands.size()-1)).getRedoModificationStamp();
+
+            return fRedoModificationStamp;
+        }
+    }
+
+    /**
+     * Internal listener to mouse and key events.
+     */
+    class KeyAndMouseListener : MouseListener, KeyListener {
+
+        /*
+         * @see MouseListener#mouseDoubleClick
+         */
+        public void mouseDoubleClick(MouseEvent e) {
+        }
+
+        /*
+         * If the right mouse button is pressed, the current editing command is closed
+         * @see MouseListener#mouseDown
+         */
+        public void mouseDown(MouseEvent e) {
+            if (e.button is 1)
+                commit();
+        }
+
+        /*
+         * @see MouseListener#mouseUp
+         */
+        public void mouseUp(MouseEvent e) {
+        }
+
+        /*
+         * @see KeyListener#keyPressed
+         */
+        public void keyReleased(KeyEvent e) {
+        }
+
+        /*
+         * On cursor keys, the current editing command is closed
+         * @see KeyListener#keyPressed
+         */
+        public void keyPressed(KeyEvent e) {
+            switch (e.keyCode) {
+                case DWT.ARROW_UP:
+                case DWT.ARROW_DOWN:
+                case DWT.ARROW_LEFT:
+                case DWT.ARROW_RIGHT:
+                    commit();
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Internal listener to document changes.
+     */
+    class DocumentListener : IDocumentListener {
+
+        private String fReplacedText;
+
+        /*
+         * @see dwtx.jface.text.IDocumentListener#documentAboutToBeChanged(dwtx.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 dwtx.jface.text.IDocumentListener#documentChanged(dwtx.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);
+            bool wasValid= false;
+            if (op !is 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 is 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 !is 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 !is fLastAddedCommand && fCurrent.isValid()) {
+                    addToCommandStack(fCurrent);
+                }
+            }
+        }
+    }
+
+    /**
+     * Internal text input listener.
+     */
+    class TextInputListener : ITextInputListener {
+
+        /*
+         * @see dwtx.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument)
+         */
+        public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
+            if (oldInput !is null && fDocumentListener !is null) {
+                oldInput.removeDocumentListener(fDocumentListener);
+                commit();
+            }
+        }
+
+        /*
+         * @see dwtx.jface.text.ITextInputListener#inputDocumentChanged(dwtx.jface.text.IDocument, dwtx.jface.text.IDocument)
+         */
+        public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
+            if (newInput !is null) {
+                if (fDocumentListener is null)
+                    fDocumentListener= new DocumentListener();
+                newInput.addDocumentListener(fDocumentListener);
+            }
+        }
+
+    }
+
+    /*
+     * @see IOperationHistoryListener
+     * @since 3.1
+     */
+    class HistoryListener : 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)) {
+                    fTextViewer.getTextWidget().getDisplay().syncExec(new Runnable() {
+                        public void run() {
+                            // if we are undoing/redoing a command we generated, then ignore
+                            // the document changes associated with this undo or redo.
+                            if (event.getOperation() instanceof TextCommand) {
+                                if (fTextViewer instanceof TextViewer)
+                                    ((TextViewer)fTextViewer).ignoreAutoEditStrategies(true);
+                                listenToTextChanges(false);
+    
+                                // in the undo case only, make sure compounds are closed
+                                if (type is OperationHistoryEvent.ABOUT_TO_UNDO) {
+                                    if (fFoldingIntoCompoundChange) {
+                                        endCompoundChange();
+                                    }
+                                }
+                            } else {
+                                // the undo or redo has our context, but it is not one of 
+                                // our commands.  We will listen to the changes, but will
+                                // reset the state that tracks the undo/redo history. 
+                                commit();
+                                fLastAddedCommand= null;
+                            }
+                        }
+                    });
+                    fOperation= event.getOperation();
+                }
+                break;
+            case OperationHistoryEvent.UNDONE:
+            case OperationHistoryEvent.REDONE:
+            case OperationHistoryEvent.OPERATION_NOT_OK:
+                if (event.getOperation() is fOperation) {
+                    fTextViewer.getTextWidget().getDisplay().syncExec(new Runnable() {
+                        public void run() {
+                            listenToTextChanges(true);
+                            fOperation= null;
+                            if (fTextViewer instanceof TextViewer)
+                                ((TextViewer)fTextViewer).ignoreAutoEditStrategies(false);
+                         }
+                    });
+                }
+                break;
+            }
+        }
+
+    }
+
+    /** Text buffer to collect text which is inserted into the viewer */
+    private StringBuffer fTextBuffer= new StringBuffer();
+    /** Text buffer to collect viewer content which has been replaced */
+    private StringBuffer fPreservedTextBuffer= new StringBuffer();
+    /** The document modification stamp for undo. */
+    protected long fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
+    /** The document modification stamp for redo. */
+    protected long fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
+    /** The internal key and mouse event listener */
+    private KeyAndMouseListener fKeyAndMouseListener;
+    /** The internal document listener */
+    private DocumentListener fDocumentListener;
+    /** The internal text input listener */
+    private TextInputListener fTextInputListener;
+
+
+    /** Indicates inserting state */
+    private bool fInserting= false;
+    /** Indicates overwriting state */
+    private bool fOverwriting= false;
+    /** Indicates whether the current change belongs to a compound change */
+    private bool fFoldingIntoCompoundChange= false;
+
+    /** The text viewer the undo manager is connected to */
+    private ITextViewer fTextViewer;
+
+    /** Supported undo level */
+    private int fUndoLevel;
+    /** The currently constructed edit command */
+    private TextCommand fCurrent;
+    /** The last delete edit command */
+    private TextCommand fPreviousDelete;
+
+    /**
+     * The undo context.
+     * @since 3.1
+     */
+    private IOperationHistory fHistory;
+    /**
+     * The operation history.
+     * @since 3.1
+     */
+    private IUndoContext fUndoContext;
+    /**
+     * The operation history listener used for managing undo and redo before
+     * and after the individual commands are performed.
+     * @since 3.1
+     */
+    private IOperationHistoryListener fHistoryListener= new HistoryListener();
+
+    /**
+     * The command 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 TextCommand fLastAddedCommand= null;
+
+    /**
+     * Creates a new undo manager who remembers the specified number of edit commands.
+     *
+     * @param undoLevel the length of this manager's history
+     */
+    public DefaultUndoManager(int undoLevel) {
+        fHistory= OperationHistoryFactory.getOperationHistory();
+        setMaximalUndoLevel(undoLevel);
+    }
+
+    /**
+     * Returns whether this undo manager is connected to a text viewer.
+     *
+     * @return <code>true</code> if connected, <code>false</code> otherwise
+     * @since 3.1
+     */
+    private bool isConnected() {
+        return fTextViewer !is null;
+    }
+
+    /*
+     * @see IUndoManager#beginCompoundChange
+     */
+    public void beginCompoundChange() {
+        if (isConnected()) {
+            fFoldingIntoCompoundChange= true;
+            commit();
+        }
+    }
+
+
+    /*
+     * @see IUndoManager#endCompoundChange
+     */
+    public void endCompoundChange() {
+        if (isConnected()) {
+            fFoldingIntoCompoundChange= false;
+            commit();
+        }
+    }
+
+    /**
+     * Registers all necessary listeners with the text viewer.
+     */
+    private void addListeners() {
+        StyledText text= fTextViewer.getTextWidget();
+        if (text !is null) {
+            fKeyAndMouseListener= new KeyAndMouseListener();
+            text.addMouseListener(fKeyAndMouseListener);
+            text.addKeyListener(fKeyAndMouseListener);
+            fTextInputListener= new TextInputListener();
+            fTextViewer.addTextInputListener(fTextInputListener);
+            fHistory.addOperationHistoryListener(fHistoryListener);
+            listenToTextChanges(true);
+        }
+    }
+
+    /**
+     * Unregister all previously installed listeners from the text viewer.
+     */
+    private void removeListeners() {
+        StyledText text= fTextViewer.getTextWidget();
+        if (text !is null) {
+            if (fKeyAndMouseListener !is null) {
+                text.removeMouseListener(fKeyAndMouseListener);
+                text.removeKeyListener(fKeyAndMouseListener);
+                fKeyAndMouseListener= null;
+            }
+            if (fTextInputListener !is null) {
+                fTextViewer.removeTextInputListener(fTextInputListener);
+                fTextInputListener= null;
+            }
+            listenToTextChanges(false);
+            fHistory.removeOperationHistoryListener(fHistoryListener);
+        }
+    }
+
+    /**
+     * Adds the given command to the operation history if it is not part of
+     * a compound change.
+     *
+     * @param command the command to be added
+     * @since 3.1
+     */
+    private void addToCommandStack(TextCommand command){
+        if (!fFoldingIntoCompoundChange || command instanceof CompoundTextCommand) {
+            fHistory.add(command);
+            fLastAddedCommand= command;
+        }
+    }
+
+    /**
+     * Disposes the command stack.
+     *
+     * @since 3.1
+     */
+    private void disposeCommandStack() {
+        fHistory.dispose(fUndoContext, true, true, true);
+    }
+
+    /**
+     * Initializes the command stack.
+     *
+     * @since 3.1
+     */
+    private void initializeCommandStack() {
+        if (fHistory !is null && fUndoContext !is null)
+            fHistory.dispose(fUndoContext, true, true, false);
+
+    }
+
+    /**
+     * Switches the state of whether there is a text listener or not.
+     *
+     * @param listen the state which should be established
+     */
+    private void listenToTextChanges(bool listen) {
+        if (listen) {
+            if (fDocumentListener is null && fTextViewer.getDocument() !is null) {
+                fDocumentListener= new DocumentListener();
+                fTextViewer.getDocument().addDocumentListener(fDocumentListener);
+            }
+        } else if (!listen) {
+            if (fDocumentListener !is null && fTextViewer.getDocument() !is null) {
+                fTextViewer.getDocument().removeDocumentListener(fDocumentListener);
+                fDocumentListener= null;
+            }
+        }
+    }
+
+    /**
+     * Closes the current editing command and opens a new one.
+     */
+    private void commit() {
+        // if fCurrent has never been placed on the command stack, do so now.
+        // this can happen when there are multiple programmatically commits in a single
+        // document change.
+        if (fLastAddedCommand !is fCurrent) {
+            fCurrent.pretendCommit();
+            if (fCurrent.isValid())
+                addToCommandStack(fCurrent);
+        }
+        fCurrent.commit();
+    }
+    
+    /**
+     * Reset processChange state.
+     *   
+     * @since 3.2
+     */
+    private void resetProcessChangeSate() {
+        fInserting= false;
+        fOverwriting= false;
+        fPreviousDelete.reinitialize();
+    }
+
+    /**
+     * Checks whether the given text starts with a line delimiter and
+     * subsequently contains a white space only.
+     *
+     * @param text the text to check
+     * @return <code>true</code> if the text is a line delimiter followed by whitespace, <code>false</code> otherwise
+     */
+    private bool isWhitespaceText(String text) {
+
+        if (text is null || text.length() is 0)
+            return false;
+
+        String[] delimiters= fTextViewer.getDocument().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 !is ' ' && c !is '\t')
+                    return false;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    private void processChange(int modelStart, int modelEnd, String insertedText, String replacedText, long beforeChangeModificationStamp, long afterChangeModificationStamp) {
+
+        if (insertedText is null)
+            insertedText= ""; //$NON-NLS-1$
+
+        if (replacedText is null)
+            replacedText= ""; //$NON-NLS-1$
+
+        int length= insertedText.length();
+        int diff= modelEnd - modelStart;
+        
+        if (fCurrent.fUndoModificationStamp is IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP)
+            fCurrent.fUndoModificationStamp= beforeChangeModificationStamp;
+
+        // normalize
+        if (diff < 0) {
+            int tmp= modelEnd;
+            modelEnd= modelStart;
+            modelStart= tmp;
+        }
+
+        if (modelStart is modelEnd) {
+            // text will be inserted
+            if ((length is 1) || isWhitespaceText(insertedText)) {
+                // by typing or whitespace
+                if (!fInserting || (modelStart !is 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 is 0) {
+                // text will be deleted by backspace or DEL key or empty clipboard
+                length= replacedText.length();
+                String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
+
+                if ((length is 1) || TextUtilities.equals(delimiters, replacedText) > -1) {
+
+                    // whereby selection is empty
+
+                    if (fPreviousDelete.fStart is modelStart && fPreviousDelete.fEnd is modelEnd) {
+                        // repeated DEL
+
+                            // correct wrong settings of fCurrent
+                        if (fCurrent.fStart is modelEnd && fCurrent.fEnd is modelStart) {
+                            fCurrent.fStart= modelStart;
+                            fCurrent.fEnd= modelEnd;
+                        }
+                            // append to buffer && extend command range
+                        fPreservedTextBuffer.append(replacedText);
+                        ++fCurrent.fEnd;
+
+                    } else if (fPreviousDelete.fStart is modelEnd) {
+                        // repeated backspace
+
+                            // insert in buffer and extend command 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 is 1) {
+                    length= replacedText.length();
+                    String[] delimiters= fTextViewer.getDocument().getLegalLineDelimiters();
+
+                    if ((length is 1) || TextUtilities.equals(delimiters, replacedText) > -1) {
+                        // because of overwrite mode or model manipulation
+                        if (!fOverwriting || (modelStart !is 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 command
+        fCurrent.fRedoModificationStamp= afterChangeModificationStamp;
+    }
+
+    /**
+     * Shows the given exception in an error dialog.
+     *
+     * @param title the dialog title
+     * @param ex the exception
+     * @since 3.1
+     */
+    private void openErrorDialog(final String title, final Exception ex) {
+        Shell shell= null;
+        if (isConnected()) {
+            StyledText st= fTextViewer.getTextWidget();
+            if (st !is null && !st.isDisposed())
+                shell= st.getShell();
+        }
+        if (Display.getCurrent() !is null)
+            MessageDialog.openError(shell, title, ex.getLocalizedMessage());
+        else {
+            Display display;
+            final Shell finalShell= shell;
+            if (finalShell !is null)
+                display= finalShell.getDisplay();
+            else
+                display= Display.getDefault();
+            display.syncExec(new Runnable() {
+                public void run() {
+                    MessageDialog.openError(finalShell, title, ex.getLocalizedMessage());
+                }
+            });
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#setMaximalUndoLevel(int)
+     */
+    public void setMaximalUndoLevel(int undoLevel) {
+        fUndoLevel= Math.max(0, undoLevel);
+        if (isConnected()) {
+            fHistory.setLimit(fUndoContext, fUndoLevel);
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#connect(dwtx.jface.text.ITextViewer)
+     */
+    public void connect(ITextViewer textViewer) {
+        if (!isConnected() && textViewer !is null) {
+            fTextViewer= textViewer;
+            if (fUndoContext is null)
+                fUndoContext= new ObjectUndoContext(this);
+
+            fHistory.setLimit(fUndoContext, fUndoLevel);
+
+            initializeCommandStack();
+
+            // open up the current command
+            fCurrent= new TextCommand(fUndoContext);
+
+            fPreviousDelete= new TextCommand(fUndoContext);
+            addListeners();
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#disconnect()
+     */
+    public void disconnect() {
+        if (isConnected()) {
+
+            removeListeners();
+
+            fCurrent= null;
+            fTextViewer= null;
+            disposeCommandStack();
+            fTextBuffer= null;
+            fPreservedTextBuffer= null;
+            fUndoContext= null;
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#reset()
+     */
+    public void reset() {
+        if (isConnected()) {
+            initializeCommandStack();
+            fCurrent= new TextCommand(fUndoContext);
+            fFoldingIntoCompoundChange= false;
+            fInserting= false;
+            fOverwriting= false;
+            fTextBuffer.setLength(0);
+            fPreservedTextBuffer.setLength(0);
+            fPreservedUndoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
+            fPreservedRedoModificationStamp= IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP;
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#redoable()
+     */
+    public bool redoable() {
+        return fHistory.canRedo(fUndoContext);
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#undoable()
+     */
+    public bool undoable() {
+        return fHistory.canUndo(fUndoContext);
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#redo()
+     */
+    public void redo() {
+        if (isConnected() && redoable()) {
+            try {
+                fHistory.redo(fUndoContext, null, null);
+            } catch (ExecutionException ex) {
+                openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.redoFailed.title"), ex); //$NON-NLS-1$
+            }
+        }
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManager#undo()
+     */
+    public void undo() {
+        if (isConnected() && undoable()) {
+            try {
+                fHistory.undo(fUndoContext, null, null);
+            } catch (ExecutionException ex) {
+                openErrorDialog(JFaceTextMessages.getString("DefaultUndoManager.error.undoFailed.title"), ex); //$NON-NLS-1$
+            }
+        }
+    }
+
+    /**
+     * Selects and reveals the specified range.
+     *
+     * @param offset the offset of the range
+     * @param length the length of the range
+     * @since 3.0
+     */
+    protected void selectAndReveal(int offset, int length) {
+        if (fTextViewer instanceof ITextViewerExtension5) {
+            ITextViewerExtension5 extension= (ITextViewerExtension5) fTextViewer;
+            extension.exposeModelRange(new Region(offset, length));
+        } else if (!fTextViewer.overlapsWithVisibleRegion(offset, length))
+            fTextViewer.resetVisibleRegion();
+
+        fTextViewer.setSelectedRange(offset, length);
+        fTextViewer.revealRange(offset, length);
+    }
+
+    /*
+     * @see dwtx.jface.text.IUndoManagerExtension#getUndoContext()
+     * @since 3.1
+     */
+    public IUndoContext getUndoContext() {
+        return fUndoContext;
+    }
+
+}