All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jface.text.DocumentCommand Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2000, 2015 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.jface.text;

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 org.eclipse.swt.events.VerifyEvent;

import org.eclipse.core.runtime.Assert;


/**
 * Represents a text modification as a document replace command. The text
 * modification is given as a {@link org.eclipse.swt.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
 * VerifyEvent.
 * 

* A document command can also represent a list of related changes.

*/ public class DocumentCommand { /** * A command which is added to document commands. * @since 2.1 */ private static class Command implements 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 null * @param owner the document command owner, may be null * @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; } /** * 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 == 0 && fText == null) return; if (fOwner != null) document.removeDocumentListener(fOwner); document.replace(fOffset, fLength, fText); if (fOwner != null) document.addDocumentListener(fOwner); } @Override public int compareTo(Command object) { if (isEqual(object)) return 0; 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 != 0) return value; } // the answer return 42; } private boolean isEqual(Object object) { if (object == this) return true; if (!(object instanceof Command)) return false; final Command command= (Command) object; return command.fOffset == fOffset && command.fLength == fLength; } } /** * An iterator, which iterates in reverse over a list. * * @param the type of elements returned by this iterator */ private static class ReverseListIterator implements 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 == null) throw new IllegalArgumentException(); fListIterator= listIterator; } @Override public boolean hasNext() { return fListIterator.hasPrevious(); } @Override public E next() { return fListIterator.previous(); } @Override public void remove() { throw new UnsupportedOperationException(); } } /** * A command iterator. */ private static class CommandIterator implements Iterator { /** The command iterator. */ private final Iterator fIterator; /** The original command. */ private Command fCommand; /** A flag indicating the direction of iteration. */ private boolean 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 boolean forward) { if (commands == null || command == null) throw new IllegalArgumentException(); fIterator= forward ? commands.iterator() : new ReverseListIterator<>(commands.listIterator(commands.size())); fCommand= command; fForward= forward; } @Override public boolean hasNext() { return fCommand != null || fIterator.hasNext(); } @Override public Command next() { if (!hasNext()) throw new NoSuchElementException(); if (fCommand == null) return fIterator.next(); if (!fIterator.hasNext()) { final Command tempCommand= fCommand; fCommand= null; return tempCommand; } final 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(); } } @Override public void remove() { throw new UnsupportedOperationException(); } } /** Must the command be updated */ public boolean 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 boolean 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 doit * 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 true if this command and the event cover the same range */ boolean fillEvent(VerifyEvent event, IRegion modelRange) { event.text= text; event.doit= (offset == modelRange.getOffset() && length == modelRange.getLength() && doit && caretOffset == -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 null, 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 null * @param commandOwner the command owner, may be null * @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 != fCommands.size() && intersects(fCommands.get(insertionIndex), command)) throw new BadLocationException(); // overlaps to the left? if (insertionIndex != 0 && intersects(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 true if the commands intersect * @since 2.1 */ private boolean 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) == 0; return true; } /** * Returns whether the given command intersects with this command. * * @param command the command * @return true if the command intersects with this command * @since 2.1 */ private boolean 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) == 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 == 0 && text == null && fCommands.size() == 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(); ) 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 true if the caret offset should be updated, false otherwise. * * @return true if the caret offset should be updated, false otherwise * @since 3.0 */ private boolean updateCaret() { return shiftsCaret && caretOffset != -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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy