diff dwtx/jface/text/DocumentCommand.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/DocumentCommand.d	Sat Aug 23 19:10:48 2008 +0200
@@ -0,0 +1,471 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 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.DocumentCommand;
+
+import dwt.dwthelper.utils;
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+import dwt.events.VerifyEvent;
+import dwtx.core.runtime.Assert;
+
+
+/**
+ * Represents a text modification as a document replace command. The text
+ * modification is given as a {@link dwt.events.VerifyEvent} and
+ * translated into a document replace command relative to a given offset. A
+ * document command can also be used to initialize a given
+ * <code>VerifyEvent</code>.
+ * <p>
+ * A document command can also represent a list of related changes.</p>
+ */
+public class DocumentCommand {
+
+    /**
+     * A command which is added to document commands.
+     * @since 2.1
+     */
+    private static class Command : Comparable {
+        /** The offset of the range to be replaced */
+        private final int fOffset;
+        /** The length of the range to be replaced. */
+        private final int fLength;
+        /** The replacement text */
+        private final String fText;
+        /** The listener who owns this command */
+        private final IDocumentListener fOwner;
+
+        /**
+         * Creates a new command with the given specification.
+         *
+         * @param offset the offset of the replace command
+         * @param length the length of the replace command
+         * @param text the text to replace with, may be <code>null</code>
+         * @param owner the document command owner, may be <code>null</code>
+         * @since 3.0
+         */
+        public Command(int offset, int length, String text, IDocumentListener owner) {
+            if (offset < 0 || length < 0)
+                throw new IllegalArgumentException();
+            fOffset= offset;
+            fLength= length;
+            fText= text;
+            fOwner= owner;
+        }
+
+        /**
+         * Returns the length delta for this command.
+         *
+         * @return the length delta for this command
+         */
+        public int getDeltaLength() {
+            return (fText is null ? 0 : fText.length()) - fLength;
+        }
+
+        /**
+         * Executes the document command on the specified document.
+         *
+         * @param document the document on which to execute the command.
+         * @throws BadLocationException in case this commands cannot be executed
+         */
+        public void execute(IDocument document) throws BadLocationException {
+
+            if (fLength is 0 && fText is null)
+                return;
+
+            if (fOwner !is null)
+                document.removeDocumentListener(fOwner);
+
+            document.replace(fOffset, fLength, fText);
+
+            if (fOwner !is null)
+                document.addDocumentListener(fOwner);
+        }
+
+        /*
+         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+         */
+        public int compareTo(final Object object) {
+            if (isEqual(object))
+                return 0;
+
+            final Command command= (Command) object;
+
+            // diff middle points if not intersecting
+            if (fOffset + fLength <= command.fOffset || command.fOffset + command.fLength <= fOffset) {
+                int value= (2 * fOffset + fLength) - (2 * command.fOffset + command.fLength);
+                if (value !is 0)
+                    return value;
+            }
+            // the answer
+            return 42;
+        }
+
+        private bool isEqual(Object object) {
+            if (object is this)
+                return true;
+            if (!(object instanceof Command))
+                return false;
+            final Command command= (Command) object;
+            return command.fOffset is fOffset && command.fLength is fLength;
+        }
+    }
+
+    /**
+     * An iterator, which iterates in reverse over a list.
+     */
+    private static class ReverseListIterator : Iterator {
+
+        /** The list iterator. */
+        private final ListIterator fListIterator;
+
+        /**
+         * Creates a reverse list iterator.
+         * @param listIterator the iterator that this reverse iterator is based upon
+         */
+        public ReverseListIterator(ListIterator listIterator) {
+            if (listIterator is null)
+                throw new IllegalArgumentException();
+            fListIterator= listIterator;
+        }
+
+        /*
+         * @see java.util.Iterator#hasNext()
+         */
+        public bool hasNext() {
+            return fListIterator.hasPrevious();
+        }
+
+        /*
+         * @see java.util.Iterator#next()
+         */
+        public Object next() {
+            return fListIterator.previous();
+        }
+
+        /*
+         * @see java.util.Iterator#remove()
+         */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * A command iterator.
+     */
+    private static class CommandIterator : Iterator {
+
+        /** The command iterator. */
+        private final Iterator fIterator;
+
+        /** The original command. */
+        private Command fCommand;
+
+        /** A flag indicating the direction of iteration. */
+        private bool fForward;
+
+        /**
+         * Creates a command iterator.
+         *
+         * @param commands an ascending ordered list of commands
+         * @param command the original command
+         * @param forward the direction
+         */
+        public CommandIterator(final List commands, final Command command, final bool forward) {
+            if (commands is null || command is null)
+                throw new IllegalArgumentException();
+            fIterator= forward ? commands.iterator() : new ReverseListIterator(commands.listIterator(commands.size()));
+            fCommand= command;
+            fForward= forward;
+        }
+
+        /*
+         * @see java.util.Iterator#hasNext()
+         */
+        public bool hasNext() {
+            return fCommand !is null || fIterator.hasNext();
+        }
+
+        /*
+         * @see java.util.Iterator#next()
+         */
+        public Object next() {
+
+            if (!hasNext())
+                throw new NoSuchElementException();
+
+            if (fCommand is null)
+                return fIterator.next();
+
+            if (!fIterator.hasNext()) {
+                final Command tempCommand= fCommand;
+                fCommand= null;
+                return tempCommand;
+            }
+
+            final Command command= (Command) fIterator.next();
+            final int compareValue= command.compareTo(fCommand);
+
+            if ((compareValue < 0) ^ !fForward) {
+                return command;
+
+            } else if ((compareValue > 0) ^ !fForward) {
+                final Command tempCommand= fCommand;
+                fCommand= command;
+                return tempCommand;
+
+            } else {
+                throw new IllegalArgumentException();
+            }
+        }
+
+        /*
+         * @see java.util.Iterator#remove()
+         */
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /** Must the command be updated */
+    public bool doit= false;
+    /** The offset of the command. */
+    public int offset;
+    /** The length of the command */
+    public int length;
+    /** The text to be inserted */
+    public String text;
+    /**
+     * The owner of the document command which will not be notified.
+     * @since 2.1
+     */
+    public IDocumentListener owner;
+    /**
+     * The caret offset with respect to the document before the document command is executed.
+     * @since 2.1
+     */
+    public int caretOffset;
+    /**
+     * Additional document commands.
+     * @since 2.1
+     */
+    private final List fCommands= new ArrayList();
+    /**
+     * Indicates whether the caret should be shifted by this command.
+     * @since 3.0
+     */
+    public bool shiftsCaret;
+
+
+    /**
+     * Creates a new document command.
+     */
+    protected DocumentCommand() {
+    }
+
+    /**
+     * Translates a verify event into a document replace command using the given offset.
+     *
+     * @param event the event to be translated
+     * @param modelRange the event range as model range
+     */
+    void setEvent(VerifyEvent event, IRegion modelRange) {
+
+        doit= true;
+        text= event.text;
+
+        offset= modelRange.getOffset();
+        length= modelRange.getLength();
+
+        owner= null;
+        caretOffset= -1;
+        shiftsCaret= true;
+        fCommands.clear();
+    }
+
+    /**
+     * Fills the given verify event with the replace text and the <code>doit</code>
+     * flag of this document command. Returns whether the document command
+     * covers the same range as the verify event considering the given offset.
+     *
+     * @param event the event to be changed
+     * @param modelRange to be considered for range comparison
+     * @return <code>true</code> if this command and the event cover the same range
+     */
+    bool fillEvent(VerifyEvent event, IRegion modelRange) {
+        event.text= text;
+        event.doit= (offset is modelRange.getOffset() && length is modelRange.getLength() && doit && caretOffset is -1);
+        return event.doit;
+    }
+
+    /**
+     * Adds an additional replace command. The added replace command must not overlap
+     * with existing ones. If the document command owner is not <code>null</code>, it will not
+     * get document change notifications for the particular command.
+     *
+     * @param commandOffset the offset of the region to replace
+     * @param commandLength the length of the region to replace
+     * @param commandText the text to replace with, may be <code>null</code>
+     * @param commandOwner the command owner, may be <code>null</code>
+     * @throws BadLocationException if the added command intersects with an existing one
+     * @since 2.1
+     */
+    public void addCommand(int commandOffset, int commandLength, String commandText, IDocumentListener commandOwner) throws BadLocationException {
+        final Command command= new Command(commandOffset, commandLength, commandText, commandOwner);
+
+        if (intersects(command))
+            throw new BadLocationException();
+
+        final int index= Collections.binarySearch(fCommands, command);
+
+        // a command with exactly the same ranges exists already
+        if (index >= 0)
+            throw new BadLocationException();
+
+        // binary search result is defined as (-(insertionIndex) - 1)
+        final int insertionIndex= -(index + 1);
+
+        // overlaps to the right?
+        if (insertionIndex !is fCommands.size() && intersects((Command) fCommands.get(insertionIndex), command))
+            throw new BadLocationException();
+
+        // overlaps to the left?
+        if (insertionIndex !is 0 && intersects((Command) fCommands.get(insertionIndex - 1), command))
+            throw new BadLocationException();
+
+        fCommands.add(insertionIndex, command);
+    }
+
+    /**
+     * Returns an iterator over the commands in ascending position order.
+     * The iterator includes the original document command.
+     * Commands cannot be removed.
+     *
+     * @return returns the command iterator
+     */
+    public Iterator getCommandIterator() {
+        Command command= new Command(offset, length, text, owner);
+        return new CommandIterator(fCommands, command, true);
+    }
+
+    /**
+     * Returns the number of commands including the original document command.
+     *
+     * @return returns the number of commands
+     * @since 2.1
+     */
+    public int getCommandCount() {
+        return 1 + fCommands.size();
+    }
+
+    /**
+     * Returns whether the two given commands intersect.
+     *
+     * @param command0 the first command
+     * @param command1 the second command
+     * @return <code>true</code> if the commands intersect
+     * @since 2.1
+     */
+    private bool intersects(Command command0, Command command1) {
+        // diff middle points if not intersecting
+        if (command0.fOffset + command0.fLength <= command1.fOffset || command1.fOffset + command1.fLength <= command0.fOffset)
+            return (2 * command0.fOffset + command0.fLength) - (2 * command1.fOffset + command1.fLength) is 0;
+        return true;
+    }
+
+    /**
+     * Returns whether the given command intersects with this command.
+     *
+     * @param command the command
+     * @return <code>true</code> if the command intersects with this command
+     * @since 2.1
+     */
+    private bool intersects(Command command) {
+        // diff middle points if not intersecting
+        if (offset + length <= command.fOffset || command.fOffset + command.fLength <= offset)
+            return (2 * offset + length) - (2 * command.fOffset + command.fLength) is 0;
+        return true;
+    }
+
+    /**
+     * Executes the document commands on a document.
+     *
+     * @param document the document on which to execute the commands
+     * @throws BadLocationException in case access to the given document fails
+     * @since 2.1
+     */
+    void execute(IDocument document) throws BadLocationException {
+
+        if (length is 0 && text is null && fCommands.size() is 0)
+            return;
+
+        DefaultPositionUpdater updater= new DefaultPositionUpdater(getCategory());
+        Position caretPosition= null;
+        try {
+            if (updateCaret()) {
+                document.addPositionCategory(getCategory());
+                document.addPositionUpdater(updater);
+                caretPosition= new Position(caretOffset);
+                document.addPosition(getCategory(), caretPosition);
+            }
+
+            final Command originalCommand= new Command(offset, length, text, owner);
+            for (final Iterator iterator= new CommandIterator(fCommands, originalCommand, false); iterator.hasNext(); )
+                ((Command) iterator.next()).execute(document);
+
+        } catch (BadLocationException e) {
+            // ignore
+        } catch (BadPositionCategoryException e) {
+            // ignore
+        } finally {
+            if (updateCaret()) {
+                document.removePositionUpdater(updater);
+                try {
+                    document.removePositionCategory(getCategory());
+                } catch (BadPositionCategoryException e) {
+                    Assert.isTrue(false);
+                }
+                caretOffset= caretPosition.getOffset();
+            }
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if the caret offset should be updated, <code>false</code> otherwise.
+     *
+     * @return <code>true</code> if the caret offset should be updated, <code>false</code> otherwise
+     * @since 3.0
+     */
+    private bool updateCaret() {
+        return shiftsCaret && caretOffset !is -1;
+    }
+
+    /**
+     * Returns the position category for the caret offset position.
+     *
+     * @return the position category for the caret offset position
+     * @since 3.0
+     */
+    private String getCategory() {
+        return toString();
+    }
+
+}