gnu.trove3;bundle-version="3.0.0",
org.simantics.scl.osgi;bundle-version="1.0.0",
org.simantics.scl.compiler;bundle-version="0.6.0",
- org.junit;bundle-version="4.12.0";resolution:=optional
+ org.junit;bundle-version="4.12.0";resolution:=optional,
+ com.ibm.icu
Export-Package: org.simantics.scl.ui.console,
org.simantics.scl.ui.editor,
org.simantics.scl.ui.editor2,
package org.simantics.scl.ui.editor2;
+import java.text.CharacterIterator;
+
+import org.eclipse.jface.action.IAction;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
+import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.link.LinkedModeModel;
+import org.eclipse.jface.text.link.LinkedPosition;
+import org.eclipse.jface.text.source.ISourceViewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.ST;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.contexts.IContextService;
import org.eclipse.ui.editors.text.TextEditor;
+import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
+import org.eclipse.ui.texteditor.IUpdate;
+import org.eclipse.ui.texteditor.TextNavigationAction;
import org.simantics.scl.ui.editor.SCLSourceViewerConfigurationNew;
import org.simantics.scl.ui.editor.completion.SCLTextEditorEnvironment;
+import org.simantics.scl.ui.editor2.iterator.DocumentCharacterIterator;
+import org.simantics.scl.ui.editor2.iterator.JavaWordIterator;
+
+import com.ibm.icu.text.BreakIterator;
public class SCLModuleEditor2 extends TextEditor {
private boolean disposed = false;
updatePartName();
}
+ @Override
+ protected void createNavigationActions() {
+ super.createNavigationActions();
+
+ // Taken from org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.createNavigationActions()
+ final StyledText textWidget= getSourceViewer().getTextWidget();
+
+ IAction action = new NavigatePreviousSubWordAction();
+ action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_PREVIOUS);
+ setAction(ITextEditorActionDefinitionIds.WORD_PREVIOUS, action);
+ textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL);
+
+ action = new NavigateNextSubWordAction();
+ action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_NEXT);
+ setAction(ITextEditorActionDefinitionIds.WORD_NEXT, action);
+ textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL);
+
+ action = new SelectPreviousSubWordAction();
+ action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS);
+ setAction(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, action);
+ textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL);
+
+ action = new SelectNextSubWordAction();
+ action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT);
+ setAction(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, action);
+ textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL);
+ }
+
protected void updatePartName() {
setPartName(getEditorInput().getName());
}
public IDocument getDocument() {
return getSourceViewer().getDocument();
}
+
+ /**
+ * Text navigation action to navigate to the next sub-word.
+ *
+ * @since 3.0
+ */
+ protected abstract class NextSubWordAction extends TextNavigationAction {
+
+ protected JavaWordIterator fIterator= new JavaWordIterator();
+
+ /**
+ * Creates a new next sub-word action.
+ *
+ * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
+ */
+ protected NextSubWordAction(int code) {
+ super(getSourceViewer().getTextWidget(), code);
+ }
+
+ /*
+ * @see org.eclipse.jface.action.IAction#run()
+ */
+ @Override
+ public void run() {
+ final ISourceViewer viewer= getSourceViewer();
+ final IDocument document= viewer.getDocument();
+ try {
+ fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
+ int position= widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset());
+ if (position == -1)
+ return;
+
+ int next= findNextPosition(position);
+ if (isBlockSelectionModeEnabled() && document.getLineOfOffset(next) != document.getLineOfOffset(position)) {
+ super.run(); // may navigate into virtual white space
+ } else if (next != BreakIterator.DONE) {
+ setCaretPosition(next);
+ getTextWidget().showSelection();
+ fireSelectionChanged();
+ }
+ } catch (BadLocationException x) {
+ // ignore
+ }
+ }
+
+ /**
+ * Finds the next position after the given position.
+ *
+ * @param position the current position
+ * @return the next position
+ */
+ protected int findNextPosition(int position) {
+ ISourceViewer viewer= getSourceViewer();
+ int widget= -1;
+ int next= position;
+ while (next != BreakIterator.DONE && widget == -1) { // XXX: optimize
+ next= fIterator.following(next);
+ if (next != BreakIterator.DONE)
+ widget= modelOffset2WidgetOffset(viewer, next);
+ }
+
+ IDocument document= viewer.getDocument();
+ LinkedModeModel model= LinkedModeModel.getModel(document, position);
+ if (model != null && next != BreakIterator.DONE) {
+ LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
+ if (linkedPosition != null) {
+ int linkedPositionEnd= linkedPosition.getOffset() + linkedPosition.getLength();
+ if (position != linkedPositionEnd && linkedPositionEnd < next)
+ next= linkedPositionEnd;
+ } else {
+ LinkedPosition nextLinkedPosition= model.findPosition(new LinkedPosition(document, next, 0));
+ if (nextLinkedPosition != null) {
+ int nextLinkedPositionOffset= nextLinkedPosition.getOffset();
+ if (position != nextLinkedPositionOffset && nextLinkedPositionOffset < next)
+ next= nextLinkedPositionOffset;
+ }
+ }
+ }
+
+ return next;
+ }
+
+ /**
+ * Sets the caret position to the sub-word boundary given with <code>position</code>.
+ *
+ * @param position Position where the action should move the caret
+ */
+ protected abstract void setCaretPosition(int position);
+ }
+
+ /**
+ * Text navigation action to navigate to the next sub-word.
+ *
+ * @since 3.0
+ */
+ protected class NavigateNextSubWordAction extends NextSubWordAction {
+
+ /**
+ * Creates a new navigate next sub-word action.
+ */
+ public NavigateNextSubWordAction() {
+ super(ST.WORD_NEXT);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
+ */
+ @Override
+ protected void setCaretPosition(final int position) {
+ getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
+ }
+ }
+
+ /**
+ * Text operation action to delete the next sub-word.
+ *
+ * @since 3.0
+ */
+ protected class DeleteNextSubWordAction extends NextSubWordAction implements IUpdate {
+
+ /**
+ * Creates a new delete next sub-word action.
+ */
+ public DeleteNextSubWordAction() {
+ super(ST.DELETE_WORD_NEXT);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
+ */
+ @Override
+ protected void setCaretPosition(final int position) {
+ if (!validateEditorInputState())
+ return;
+
+ final ISourceViewer viewer= getSourceViewer();
+ StyledText text= viewer.getTextWidget();
+ Point widgetSelection= text.getSelection();
+ if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) {
+ final int caret= text.getCaretOffset();
+ final int offset= modelOffset2WidgetOffset(viewer, position);
+
+ if (caret == widgetSelection.x)
+ text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y);
+ else
+ text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x);
+ text.invokeAction(ST.DELETE_NEXT);
+ } else {
+ Point selection= viewer.getSelectedRange();
+ final int caret, length;
+ if (selection.y != 0) {
+ caret= selection.x;
+ length= selection.y;
+ } else {
+ caret= widgetOffset2ModelOffset(viewer, text.getCaretOffset());
+ length= position - caret;
+ }
+
+ try {
+ viewer.getDocument().replace(caret, length, ""); //$NON-NLS-1$
+ } catch (BadLocationException exception) {
+ // Should not happen
+ }
+ }
+ }
+
+ /*
+ * @see org.eclipse.ui.texteditor.IUpdate#update()
+ */
+ public void update() {
+ setEnabled(isEditorInputModifiable());
+ }
+ }
+
+ /**
+ * Text operation action to select the next sub-word.
+ *
+ * @since 3.0
+ */
+ protected class SelectNextSubWordAction extends NextSubWordAction {
+
+ /**
+ * Creates a new select next sub-word action.
+ */
+ public SelectNextSubWordAction() {
+ super(ST.SELECT_WORD_NEXT);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.NextSubWordAction#setCaretPosition(int)
+ */
+ @Override
+ protected void setCaretPosition(final int position) {
+ final ISourceViewer viewer= getSourceViewer();
+
+ final StyledText text= viewer.getTextWidget();
+ if (text != null && !text.isDisposed()) {
+
+ final Point selection= text.getSelection();
+ final int caret= text.getCaretOffset();
+ final int offset= modelOffset2WidgetOffset(viewer, position);
+
+ if (caret == selection.x)
+ text.setSelectionRange(selection.y, offset - selection.y);
+ else
+ text.setSelectionRange(selection.x, offset - selection.x);
+ }
+ }
+ }
+
+ /**
+ * Text navigation action to navigate to the previous sub-word.
+ *
+ * @since 3.0
+ */
+ protected abstract class PreviousSubWordAction extends TextNavigationAction {
+
+ protected JavaWordIterator fIterator= new JavaWordIterator();
+
+ /**
+ * Creates a new previous sub-word action.
+ *
+ * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST.
+ */
+ protected PreviousSubWordAction(final int code) {
+ super(getSourceViewer().getTextWidget(), code);
+ }
+
+ /*
+ * @see org.eclipse.jface.action.IAction#run()
+ */
+ @Override
+ public void run() {
+ final ISourceViewer viewer= getSourceViewer();
+ final IDocument document= viewer.getDocument();
+ try {
+ fIterator.setText((CharacterIterator)new DocumentCharacterIterator(document));
+ int position= widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset());
+ if (position == -1)
+ return;
+
+ int previous= findPreviousPosition(position);
+ if (isBlockSelectionModeEnabled() && document.getLineOfOffset(previous) != document.getLineOfOffset(position)) {
+ super.run(); // may navigate into virtual white space
+ } else if (previous != BreakIterator.DONE) {
+ setCaretPosition(previous);
+ getTextWidget().showSelection();
+ fireSelectionChanged();
+ }
+ } catch (BadLocationException x) {
+ // ignore - getLineOfOffset failed
+ }
+
+ }
+
+ /**
+ * Finds the previous position before the given position.
+ *
+ * @param position the current position
+ * @return the previous position
+ */
+ protected int findPreviousPosition(int position) {
+ ISourceViewer viewer= getSourceViewer();
+ int widget= -1;
+ int previous= position;
+ while (previous != BreakIterator.DONE && widget == -1) { // XXX: optimize
+ previous= fIterator.preceding(previous);
+ if (previous != BreakIterator.DONE)
+ widget= modelOffset2WidgetOffset(viewer, previous);
+ }
+
+ IDocument document= viewer.getDocument();
+ LinkedModeModel model= LinkedModeModel.getModel(document, position);
+ if (model != null && previous != BreakIterator.DONE) {
+ LinkedPosition linkedPosition= model.findPosition(new LinkedPosition(document, position, 0));
+ if (linkedPosition != null) {
+ int linkedPositionOffset= linkedPosition.getOffset();
+ if (position != linkedPositionOffset && previous < linkedPositionOffset)
+ previous= linkedPositionOffset;
+ } else {
+ LinkedPosition previousLinkedPosition= model.findPosition(new LinkedPosition(document, previous, 0));
+ if (previousLinkedPosition != null) {
+ int previousLinkedPositionEnd= previousLinkedPosition.getOffset() + previousLinkedPosition.getLength();
+ if (position != previousLinkedPositionEnd && previous < previousLinkedPositionEnd)
+ previous= previousLinkedPositionEnd;
+ }
+ }
+ }
+
+ return previous;
+ }
+
+ /**
+ * Sets the caret position to the sub-word boundary given with <code>position</code>.
+ *
+ * @param position Position where the action should move the caret
+ */
+ protected abstract void setCaretPosition(int position);
+ }
+
+ /**
+ * Text navigation action to navigate to the previous sub-word.
+ *
+ * @since 3.0
+ */
+ protected class NavigatePreviousSubWordAction extends PreviousSubWordAction {
+
+ /**
+ * Creates a new navigate previous sub-word action.
+ */
+ public NavigatePreviousSubWordAction() {
+ super(ST.WORD_PREVIOUS);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
+ */
+ @Override
+ protected void setCaretPosition(final int position) {
+ getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position));
+ }
+ }
+
+ /**
+ * Text operation action to delete the previous sub-word.
+ *
+ * @since 3.0
+ */
+ protected class DeletePreviousSubWordAction extends PreviousSubWordAction implements IUpdate {
+
+ /**
+ * Creates a new delete previous sub-word action.
+ */
+ public DeletePreviousSubWordAction() {
+ super(ST.DELETE_WORD_PREVIOUS);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
+ */
+ @Override
+ protected void setCaretPosition(int position) {
+ if (!validateEditorInputState())
+ return;
+
+ final int length;
+ final ISourceViewer viewer= getSourceViewer();
+ StyledText text= viewer.getTextWidget();
+ Point widgetSelection= text.getSelection();
+ if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) {
+ final int caret= text.getCaretOffset();
+ final int offset= modelOffset2WidgetOffset(viewer, position);
+
+ if (caret == widgetSelection.x)
+ text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y);
+ else
+ text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x);
+ text.invokeAction(ST.DELETE_PREVIOUS);
+ } else {
+ Point selection= viewer.getSelectedRange();
+ if (selection.y != 0) {
+ position= selection.x;
+ length= selection.y;
+ } else {
+ length= widgetOffset2ModelOffset(viewer, text.getCaretOffset()) - position;
+ }
+
+ try {
+ viewer.getDocument().replace(position, length, ""); //$NON-NLS-1$
+ } catch (BadLocationException exception) {
+ // Should not happen
+ }
+ }
+ }
+
+ /*
+ * @see org.eclipse.ui.texteditor.IUpdate#update()
+ */
+ public void update() {
+ setEnabled(isEditorInputModifiable());
+ }
+ }
+
+ /**
+ * Text operation action to select the previous sub-word.
+ *
+ * @since 3.0
+ */
+ protected class SelectPreviousSubWordAction extends PreviousSubWordAction {
+
+ /**
+ * Creates a new select previous sub-word action.
+ */
+ public SelectPreviousSubWordAction() {
+ super(ST.SELECT_WORD_PREVIOUS);
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.javaeditor.JavaEditor.PreviousSubWordAction#setCaretPosition(int)
+ */
+ @Override
+ protected void setCaretPosition(final int position) {
+ final ISourceViewer viewer= getSourceViewer();
+
+ final StyledText text= viewer.getTextWidget();
+ if (text != null && !text.isDisposed()) {
+
+ final Point selection= text.getSelection();
+ final int caret= text.getCaretOffset();
+ final int offset= modelOffset2WidgetOffset(viewer, position);
+
+ if (caret == selection.x)
+ text.setSelectionRange(selection.y, offset - selection.y);
+ else
+ text.setSelectionRange(selection.x, offset - selection.x);
+ }
+ }
+ }
+
}
--- /dev/null
+package org.simantics.scl.ui.editor2.iterator;
+
+import java.text.CharacterIterator;
+
+import org.eclipse.core.runtime.Assert;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+
+
+/**
+ * An <code>IDocument</code> based implementation of
+ * <code>CharacterIterator</code> and <code>CharSequence</code>. Note that
+ * the supplied document is not copied; if the document is modified during the
+ * lifetime of a <code>DocumentCharacterIterator</code>, the methods
+ * returning document content may not always return the same values. Also, if
+ * accessing the document fails with a {@link BadLocationException}, any of
+ * <code>CharacterIterator</code> methods as well as <code>charAt</code>may
+ * return {@link CharacterIterator#DONE}.
+ *
+ * @since 3.0
+ */
+public class DocumentCharacterIterator implements CharacterIterator, CharSequence {
+
+ private int fIndex= -1;
+ private final IDocument fDocument;
+ private final int fFirst;
+ private final int fLast;
+
+ private void invariant() {
+ Assert.isTrue(fIndex >= fFirst);
+ Assert.isTrue(fIndex <= fLast);
+ }
+
+ /**
+ * Creates an iterator for the entire document.
+ *
+ * @param document the document backing this iterator
+ * @throws BadLocationException if the indices are out of bounds
+ */
+ public DocumentCharacterIterator(IDocument document) throws BadLocationException {
+ this(document, 0);
+ }
+
+ /**
+ * Creates an iterator, starting at offset <code>first</code>.
+ *
+ * @param document the document backing this iterator
+ * @param first the first character to consider
+ * @throws BadLocationException if the indices are out of bounds
+ */
+ public DocumentCharacterIterator(IDocument document, int first) throws BadLocationException {
+ this(document, first, document.getLength());
+ }
+
+ /**
+ * Creates an iterator for the document contents from <code>first</code> (inclusive) to
+ * <code>last</code> (exclusive).
+ *
+ * @param document the document backing this iterator
+ * @param first the first character to consider
+ * @param last the last character index to consider
+ * @throws BadLocationException if the indices are out of bounds
+ */
+ public DocumentCharacterIterator(IDocument document, int first, int last) throws BadLocationException {
+ if (document == null)
+ throw new NullPointerException();
+ if (first < 0 || first > last)
+ throw new BadLocationException();
+ if (last > document.getLength()) {
+ throw new BadLocationException();
+ }
+ fDocument= document;
+ fFirst= first;
+ fLast= last;
+ fIndex= first;
+ invariant();
+ }
+
+ /*
+ * @see java.text.CharacterIterator#first()
+ */
+ public char first() {
+ return setIndex(getBeginIndex());
+ }
+
+ /*
+ * @see java.text.CharacterIterator#last()
+ */
+ public char last() {
+ if (fFirst == fLast)
+ return setIndex(getEndIndex());
+ else
+ return setIndex(getEndIndex() - 1);
+ }
+
+ /*
+ * @see java.text.CharacterIterator#current()
+ */
+ public char current() {
+ if (fIndex >= fFirst && fIndex < fLast)
+ try {
+ return fDocument.getChar(fIndex);
+ } catch (BadLocationException e) {
+ // ignore
+ }
+ return DONE;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#next()
+ */
+ public char next() {
+ return setIndex(Math.min(fIndex + 1, getEndIndex()));
+ }
+
+ /*
+ * @see java.text.CharacterIterator#previous()
+ */
+ public char previous() {
+ if (fIndex > getBeginIndex()) {
+ return setIndex(fIndex - 1);
+ } else {
+ return DONE;
+ }
+ }
+
+ /*
+ * @see java.text.CharacterIterator#setIndex(int)
+ */
+ public char setIndex(int position) {
+ if (position >= getBeginIndex() && position <= getEndIndex())
+ fIndex= position;
+ else
+ throw new IllegalArgumentException();
+
+ invariant();
+ return current();
+ }
+
+ /*
+ * @see java.text.CharacterIterator#getBeginIndex()
+ */
+ public int getBeginIndex() {
+ return fFirst;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#getEndIndex()
+ */
+ public int getEndIndex() {
+ return fLast;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#getIndex()
+ */
+ public int getIndex() {
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#clone()
+ */
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new InternalError();
+ }
+ }
+
+ /*
+ * @see java.lang.CharSequence#length()
+ */
+ public int length() {
+ return getEndIndex() - getBeginIndex();
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * Note that, if the document is modified concurrently, this method may
+ * return {@link CharacterIterator#DONE} if a {@link BadLocationException}
+ * was thrown when accessing the backing document.
+ * </p>
+ *
+ * @param index {@inheritDoc}
+ * @return {@inheritDoc}
+ */
+ public char charAt(int index) {
+ if (index >= 0 && index < length())
+ try {
+ return fDocument.getChar(getBeginIndex() + index);
+ } catch (BadLocationException e) {
+ // ignore and return DONE
+ return DONE;
+ }
+ else
+ throw new IndexOutOfBoundsException();
+ }
+
+ /*
+ * @see java.lang.CharSequence#subSequence(int, int)
+ */
+ public CharSequence subSequence(int start, int end) {
+ if (start < 0)
+ throw new IndexOutOfBoundsException();
+ if (end < start)
+ throw new IndexOutOfBoundsException();
+ if (end > length())
+ throw new IndexOutOfBoundsException();
+ try {
+ return new DocumentCharacterIterator(fDocument, getBeginIndex() + start, getBeginIndex() + end);
+ } catch (BadLocationException ex) {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+}
--- /dev/null
+package org.simantics.scl.ui.editor2.iterator;
+
+import java.text.CharacterIterator;
+
+import com.ibm.icu.text.BreakIterator;
+
+import org.eclipse.core.runtime.Assert;
+
+
+
+/**
+ * A java break iterator. It returns all breaks, including before and after
+ * whitespace, and it returns all camel case breaks.
+ * <p>
+ * A line break may be any of "\n", "\r", "\r\n", "\n\r".
+ * </p>
+ *
+ * @since 3.0
+ */
+public class JavaBreakIterator extends BreakIterator {
+
+ /**
+ * A run of common characters.
+ */
+ protected static abstract class Run {
+ /** The length of this run. */
+ protected int length;
+
+ public Run() {
+ init();
+ }
+
+ /**
+ * Returns <code>true</code> if this run consumes <code>ch</code>,
+ * <code>false</code> otherwise. If <code>true</code> is returned,
+ * the length of the receiver is adjusted accordingly.
+ *
+ * @param ch the character to test
+ * @return <code>true</code> if <code>ch</code> was consumed
+ */
+ protected boolean consume(char ch) {
+ if (isValid(ch)) {
+ length++;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Whether this run accepts that character; does not update state. Called
+ * from the default implementation of <code>consume</code>.
+ *
+ * @param ch the character to test
+ * @return <code>true</code> if <code>ch</code> is accepted
+ */
+ protected abstract boolean isValid(char ch);
+
+ /**
+ * Resets this run to the initial state.
+ */
+ protected void init() {
+ length= 0;
+ }
+ }
+
+ static final class Whitespace extends Run {
+ @Override
+ protected boolean isValid(char ch) {
+ return Character.isWhitespace(ch) && ch != '\n' && ch != '\r';
+ }
+ }
+
+ static final class LineDelimiter extends Run {
+ /** State: INIT -> delimiter -> EXIT. */
+ private char fState;
+ private static final char INIT= '\0';
+ private static final char EXIT= '\1';
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.JavaBreakIterator.Run#init()
+ */
+ @Override
+ protected void init() {
+ super.init();
+ fState= INIT;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.JavaBreakIterator.Run#consume(char)
+ */
+ @Override
+ protected boolean consume(char ch) {
+ if (!isValid(ch) || fState == EXIT)
+ return false;
+
+ if (fState == INIT) {
+ fState= ch;
+ length++;
+ return true;
+ } else if (fState != ch) {
+ fState= EXIT;
+ length++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected boolean isValid(char ch) {
+ return ch == '\n' || ch == '\r';
+ }
+ }
+
+ static final class Identifier extends Run {
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.JavaBreakIterator.Run#isValid(char)
+ */
+ @Override
+ protected boolean isValid(char ch) {
+ return Character.isJavaIdentifierPart(ch);
+ }
+ }
+
+ static final class CamelCaseIdentifier extends Run {
+ /* states */
+ private static final int S_INIT= 0;
+ private static final int S_LOWER= 1;
+ private static final int S_ONE_CAP= 2;
+ private static final int S_ALL_CAPS= 3;
+ private static final int S_EXIT= 4;
+ private static final int S_EXIT_MINUS_ONE= 5;
+
+ /* character types */
+ private static final int K_INVALID= 0;
+ private static final int K_LOWER= 1;
+ private static final int K_UPPER= 2;
+ private static final int K_OTHER= 3;
+
+ private int fState;
+
+ private final static int[][] MATRIX= new int[][] {
+ // K_INVALID, K_LOWER, K_UPPER, K_OTHER
+ { S_EXIT, S_LOWER, S_ONE_CAP, S_LOWER }, // S_INIT
+ { S_EXIT, S_LOWER, S_EXIT, S_LOWER }, // S_LOWER
+ { S_EXIT, S_LOWER, S_ALL_CAPS, S_LOWER }, // S_ONE_CAP
+ { S_EXIT, S_EXIT_MINUS_ONE, S_ALL_CAPS, S_LOWER }, // S_ALL_CAPS
+ };
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.JavaBreakIterator.Run#init()
+ */
+ @Override
+ protected void init() {
+ super.init();
+ fState= S_INIT;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.JavaBreakIterator.Run#consumes(char)
+ */
+ @Override
+ protected boolean consume(char ch) {
+ int kind= getKind(ch);
+ fState= MATRIX[fState][kind];
+ switch (fState) {
+ case S_LOWER:
+ case S_ONE_CAP:
+ case S_ALL_CAPS:
+ length++;
+ return true;
+ case S_EXIT:
+ return false;
+ case S_EXIT_MINUS_ONE:
+ length--;
+ return false;
+ default:
+ Assert.isTrue(false);
+ return false;
+ }
+ }
+
+ /**
+ * Determines the kind of a character.
+ *
+ * @param ch the character to test
+ */
+ private int getKind(char ch) {
+ if (Character.isUpperCase(ch))
+ return K_UPPER;
+ if (Character.isLowerCase(ch))
+ return K_LOWER;
+ if (Character.isJavaIdentifierPart(ch)) // _, digits...
+ return K_OTHER;
+ return K_INVALID;
+ }
+
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.JavaBreakIterator.Run#isValid(char)
+ */
+ @Override
+ protected boolean isValid(char ch) {
+ return Character.isJavaIdentifierPart(ch);
+ }
+ }
+
+ static final class Other extends Run {
+ /*
+ * @see org.eclipse.jdt.internal.ui.text.JavaBreakIterator.Run#isValid(char)
+ */
+ @Override
+ protected boolean isValid(char ch) {
+ return !Character.isWhitespace(ch) && !Character.isJavaIdentifierPart(ch);
+ }
+ }
+
+ private static final Run WHITESPACE= new Whitespace();
+ private static final Run DELIMITER= new LineDelimiter();
+ private static final Run CAMELCASE= new CamelCaseIdentifier(); // new Identifier();
+ private static final Run OTHER= new Other();
+
+ /** The platform break iterator (word instance) used as a base. */
+ protected final BreakIterator fIterator;
+ /** The text we operate on. */
+ protected CharSequence fText;
+ /** our current position for the stateful methods. */
+ private int fIndex;
+
+
+ /**
+ * Creates a new break iterator.
+ */
+ public JavaBreakIterator() {
+ fIterator= BreakIterator.getWordInstance();
+ fIndex= fIterator.current();
+ }
+
+ /*
+ * @see java.text.BreakIterator#current()
+ */
+ @Override
+ public int current() {
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#first()
+ */
+ @Override
+ public int first() {
+ fIndex= fIterator.first();
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#following(int)
+ */
+ @Override
+ public int following(int offset) {
+ // work around too eager IAEs in standard implementation
+ if (offset == getText().getEndIndex())
+ return DONE;
+
+ int next= fIterator.following(offset);
+ if (next == DONE)
+ return DONE;
+
+ // TODO deal with complex script word boundaries
+ // Math.min(offset + run.length, next) does not work
+ // since BreakIterator.getWordInstance considers _ as boundaries
+ // seems to work fine, however
+ Run run= consumeRun(offset);
+ return offset + run.length;
+
+ }
+
+ /**
+ * Consumes a run of characters at the limits of which we introduce a break.
+ * @param offset the offset to start at
+ * @return the run that was consumed
+ */
+ private Run consumeRun(int offset) {
+ // assert offset < length
+
+ char ch= fText.charAt(offset);
+ int length= fText.length();
+ Run run= getRun(ch);
+ while (run.consume(ch) && offset < length - 1) {
+ offset++;
+ ch= fText.charAt(offset);
+ }
+
+ return run;
+ }
+
+ /**
+ * Returns a run based on a character.
+ *
+ * @param ch the character to test
+ * @return the correct character given <code>ch</code>
+ */
+ private Run getRun(char ch) {
+ Run run;
+ if (WHITESPACE.isValid(ch))
+ run= WHITESPACE;
+ else if (DELIMITER.isValid(ch))
+ run= DELIMITER;
+ else if (CAMELCASE.isValid(ch))
+ run= CAMELCASE;
+ else if (OTHER.isValid(ch))
+ run= OTHER;
+ else {
+ Assert.isTrue(false);
+ return null;
+ }
+
+ run.init();
+ return run;
+ }
+
+ /*
+ * @see java.text.BreakIterator#getText()
+ */
+ @Override
+ public CharacterIterator getText() {
+ return fIterator.getText();
+ }
+
+ /*
+ * @see java.text.BreakIterator#isBoundary(int)
+ */
+ @Override
+ public boolean isBoundary(int offset) {
+ if (offset == getText().getBeginIndex())
+ return true;
+ else
+ return following(offset - 1) == offset;
+ }
+
+ /*
+ * @see java.text.BreakIterator#last()
+ */
+ @Override
+ public int last() {
+ fIndex= fIterator.last();
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#next()
+ */
+ @Override
+ public int next() {
+ fIndex= following(fIndex);
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#next(int)
+ */
+ @Override
+ public int next(int n) {
+ return fIterator.next(n);
+ }
+
+ /*
+ * @see java.text.BreakIterator#preceding(int)
+ */
+ @Override
+ public int preceding(int offset) {
+ if (offset == getText().getBeginIndex())
+ return DONE;
+
+ if (isBoundary(offset - 1))
+ return offset - 1;
+
+ int previous= offset - 1;
+ do {
+ previous= fIterator.preceding(previous);
+ } while (!isBoundary(previous));
+
+ int last= DONE;
+ while (previous < offset) {
+ last= previous;
+ previous= following(previous);
+ }
+
+ return last;
+ }
+
+ /*
+ * @see java.text.BreakIterator#previous()
+ */
+ @Override
+ public int previous() {
+ fIndex= preceding(fIndex);
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#setText(java.lang.String)
+ */
+ @Override
+ public void setText(String newText) {
+ setText((CharSequence) newText);
+ }
+
+ /**
+ * Creates a break iterator given a char sequence.
+ * @param newText the new text
+ */
+ public void setText(CharSequence newText) {
+ fText= newText;
+ fIterator.setText(new SequenceCharacterIterator(newText));
+ first();
+ }
+
+ /*
+ * @see java.text.BreakIterator#setText(java.text.CharacterIterator)
+ */
+ @Override
+ public void setText(CharacterIterator newText) {
+ if (newText instanceof CharSequence) {
+ fText= (CharSequence) newText;
+ fIterator.setText(newText);
+ first();
+ } else {
+ throw new UnsupportedOperationException("CharacterIterator not supported"); //$NON-NLS-1$
+ }
+ }
+}
--- /dev/null
+package org.simantics.scl.ui.editor2.iterator;
+
+import java.text.CharacterIterator;
+
+import com.ibm.icu.text.BreakIterator;
+
+import org.eclipse.core.runtime.Assert;
+
+
+
+/**
+ * Breaks java text into word starts, also stops at line start and end. No
+ * direction dependency.
+ *
+ * @since 3.0
+ */
+public class JavaWordIterator extends BreakIterator {
+
+ /**
+ * The underlying java break iterator. It returns all breaks, including
+ * before and after every whitespace.
+ */
+ private JavaBreakIterator fIterator;
+ /** The current index for the stateful operations. */
+ private int fIndex;
+
+ /**
+ * Creates a new word iterator.
+ */
+ public JavaWordIterator() {
+ fIterator= new JavaBreakIterator();
+ first();
+ }
+
+ /*
+ * @see java.text.BreakIterator#first()
+ */
+ @Override
+ public int first() {
+ fIndex= fIterator.first();
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#last()
+ */
+ @Override
+ public int last() {
+ fIndex= fIterator.last();
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#next(int)
+ */
+ @Override
+ public int next(int n) {
+ int next= 0;
+ while (--n > 0 && next != DONE) {
+ next= next();
+ }
+ return next;
+ }
+
+ /*
+ * @see java.text.BreakIterator#next()
+ */
+ @Override
+ public int next() {
+ fIndex= following(fIndex);
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#previous()
+ */
+ @Override
+ public int previous() {
+ fIndex= preceding(fIndex);
+ return fIndex;
+ }
+
+
+ /*
+ * @see java.text.BreakIterator#preceding(int)
+ */
+ @Override
+ public int preceding(int offset) {
+ int first= fIterator.preceding(offset);
+ if (isWhitespace(first, offset)) {
+ int second= fIterator.preceding(first);
+ if (second != DONE && !isDelimiter(second, first))
+ return second;
+ }
+ return first;
+ }
+
+ /*
+ * @see java.text.BreakIterator#following(int)
+ */
+ @Override
+ public int following(int offset) {
+ int first= fIterator.following(offset);
+ if (eatFollowingWhitespace(offset, first)) {
+ int second= fIterator.following(first);
+ if (isWhitespace(first, second))
+ return second;
+ }
+ return first;
+ }
+
+ private boolean eatFollowingWhitespace(int offset, int exclusiveEnd) {
+ if (exclusiveEnd == DONE || offset == DONE)
+ return false;
+
+ if (isWhitespace(offset, exclusiveEnd))
+ return false;
+ if (isDelimiter(offset, exclusiveEnd))
+ return false;
+
+ return true;
+ }
+
+ /**
+ * Returns <code>true</code> if the given sequence into the underlying text
+ * represents a delimiter, <code>false</code> otherwise.
+ *
+ * @param offset the offset
+ * @param exclusiveEnd the end offset
+ * @return <code>true</code> if the given range is a delimiter
+ */
+ private boolean isDelimiter(int offset, int exclusiveEnd) {
+ if (exclusiveEnd == DONE || offset == DONE)
+ return false;
+
+ Assert.isTrue(offset >= 0);
+ Assert.isTrue(exclusiveEnd <= getText().getEndIndex());
+ Assert.isTrue(exclusiveEnd > offset);
+
+ CharSequence seq= fIterator.fText;
+
+ while (offset < exclusiveEnd) {
+ char ch= seq.charAt(offset);
+ if (ch != '\n' && ch != '\r')
+ return false;
+ offset++;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns <code>true</code> if the given sequence into the underlying text
+ * represents whitespace, but not a delimiter, <code>false</code> otherwise.
+ *
+ * @param offset the offset
+ * @param exclusiveEnd the end offset
+ * @return <code>true</code> if the given range is whitespace
+ */
+ private boolean isWhitespace(int offset, int exclusiveEnd) {
+ if (exclusiveEnd == DONE || offset == DONE)
+ return false;
+
+ Assert.isTrue(offset >= 0);
+ Assert.isTrue(exclusiveEnd <= getText().getEndIndex());
+ Assert.isTrue(exclusiveEnd > offset);
+
+ CharSequence seq= fIterator.fText;
+
+ while (offset < exclusiveEnd) {
+ char ch= seq.charAt(offset);
+ if (!Character.isWhitespace(ch))
+ return false;
+ if (ch == '\n' || ch == '\r')
+ return false;
+ offset++;
+ }
+
+ return true;
+ }
+
+ /*
+ * @see java.text.BreakIterator#current()
+ */
+ @Override
+ public int current() {
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.BreakIterator#getText()
+ */
+ @Override
+ public CharacterIterator getText() {
+ return fIterator.getText();
+ }
+
+ /**
+ * Sets the text as <code>CharSequence</code>.
+ * @param newText the new text
+ */
+ public void setText(CharSequence newText) {
+ fIterator.setText(newText);
+ first();
+ }
+
+ /*
+ * @see java.text.BreakIterator#setText(java.text.CharacterIterator)
+ */
+ @Override
+ public void setText(CharacterIterator newText) {
+ fIterator.setText(newText);
+ first();
+ }
+
+ /*
+ * @see java.text.BreakIterator#setText(java.lang.String)
+ */
+ @Override
+ public void setText(String newText) {
+ setText((CharSequence) newText);
+ }
+
+}
--- /dev/null
+package org.simantics.scl.ui.editor2.iterator;
+
+import java.text.CharacterIterator;
+
+import org.eclipse.core.runtime.Assert;
+
+
+
+/**
+ * A <code>CharSequence</code> based implementation of <code>CharacterIterator</code>.
+ *
+ * @since 3.0
+ */
+public class SequenceCharacterIterator implements CharacterIterator {
+
+ private int fIndex= -1;
+ private final CharSequence fSequence;
+ private final int fFirst;
+ private final int fLast;
+
+ private void invariant() {
+ Assert.isTrue(fIndex >= fFirst);
+ Assert.isTrue(fIndex <= fLast);
+ }
+
+ /**
+ * Creates an iterator for the entire sequence.
+ *
+ * @param sequence the sequence backing this iterator
+ */
+ public SequenceCharacterIterator(CharSequence sequence) {
+ this(sequence, 0);
+ }
+
+ /**
+ * Creates an iterator.
+ *
+ * @param sequence the sequence backing this iterator
+ * @param first the first character to consider
+ * @throws IllegalArgumentException if the indices are out of bounds
+ */
+ public SequenceCharacterIterator(CharSequence sequence, int first) throws IllegalArgumentException {
+ this(sequence, first, sequence.length());
+ }
+
+ /**
+ * Creates an iterator.
+ *
+ * @param sequence the sequence backing this iterator
+ * @param first the first character to consider
+ * @param last the last character index to consider
+ * @throws IllegalArgumentException if the indices are out of bounds
+ */
+ public SequenceCharacterIterator(CharSequence sequence, int first, int last) throws IllegalArgumentException {
+ if (sequence == null)
+ throw new NullPointerException();
+ if (first < 0 || first > last)
+ throw new IllegalArgumentException();
+ if (last > sequence.length())
+ throw new IllegalArgumentException();
+ fSequence= sequence;
+ fFirst= first;
+ fLast= last;
+ fIndex= first;
+ invariant();
+ }
+
+ /*
+ * @see java.text.CharacterIterator#first()
+ */
+ public char first() {
+ return setIndex(getBeginIndex());
+ }
+
+ /*
+ * @see java.text.CharacterIterator#last()
+ */
+ public char last() {
+ if (fFirst == fLast)
+ return setIndex(getEndIndex());
+ else
+ return setIndex(getEndIndex() - 1);
+ }
+
+ /*
+ * @see java.text.CharacterIterator#current()
+ */
+ public char current() {
+ if (fIndex >= fFirst && fIndex < fLast)
+ return fSequence.charAt(fIndex);
+ else
+ return DONE;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#next()
+ */
+ public char next() {
+ return setIndex(Math.min(fIndex + 1, getEndIndex()));
+ }
+
+ /*
+ * @see java.text.CharacterIterator#previous()
+ */
+ public char previous() {
+ if (fIndex > getBeginIndex()) {
+ return setIndex(fIndex - 1);
+ } else {
+ return DONE;
+ }
+ }
+
+ /*
+ * @see java.text.CharacterIterator#setIndex(int)
+ */
+ public char setIndex(int position) {
+ if (position >= getBeginIndex() && position <= getEndIndex())
+ fIndex= position;
+ else
+ throw new IllegalArgumentException();
+
+ invariant();
+ return current();
+ }
+
+ /*
+ * @see java.text.CharacterIterator#getBeginIndex()
+ */
+ public int getBeginIndex() {
+ return fFirst;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#getEndIndex()
+ */
+ public int getEndIndex() {
+ return fLast;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#getIndex()
+ */
+ public int getIndex() {
+ return fIndex;
+ }
+
+ /*
+ * @see java.text.CharacterIterator#clone()
+ */
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new InternalError();
+ }
+ }
+}