Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/StructuredDocumentToTextAdapter.java')
-rw-r--r--core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/StructuredDocumentToTextAdapter.java1332
1 files changed, 1332 insertions, 0 deletions
diff --git a/core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/StructuredDocumentToTextAdapter.java b/core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/StructuredDocumentToTextAdapter.java
new file mode 100644
index 0000000000..4a276fd591
--- /dev/null
+++ b/core/bundles/org.eclipse.wst.sse.ui/src/org/eclipse/wst/sse/ui/internal/StructuredDocumentToTextAdapter.java
@@ -0,0 +1,1332 @@
+/*******************************************************************************
+ * Copyright (c) 2001, 2005 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
+ * Jens Lukowski/Innoopract - initial renaming/restructuring
+ *
+ *******************************************************************************/
+package org.eclipse.wst.sse.ui.internal;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.jobs.ILock;
+import org.eclipse.jface.text.AbstractDocument;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.ConfigurableLineTracker;
+import org.eclipse.jface.text.DefaultLineTracker;
+import org.eclipse.jface.text.DocumentEvent;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IDocumentAdapter;
+import org.eclipse.jface.text.IDocumentAdapterExtension;
+import org.eclipse.jface.text.IDocumentListener;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.IRepairableDocument;
+import org.eclipse.jface.text.ITextStore;
+import org.eclipse.jface.text.Region;
+import org.eclipse.jface.text.TextUtilities;
+import org.eclipse.jface.text.projection.ProjectionDocument;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.custom.TextChangeListener;
+import org.eclipse.swt.custom.TextChangedEvent;
+import org.eclipse.swt.custom.TextChangingEvent;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.wst.sse.core.internal.ILockable;
+import org.eclipse.wst.sse.core.internal.provisional.events.IStructuredDocumentListener;
+import org.eclipse.wst.sse.core.internal.provisional.events.NewDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.NoChangeEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionChangedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.RegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentEvent;
+import org.eclipse.wst.sse.core.internal.provisional.events.StructuredDocumentRegionsReplacedEvent;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocument;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.IStructuredDocumentRegionList;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegion;
+import org.eclipse.wst.sse.core.internal.provisional.text.ITextRegionList;
+import org.eclipse.wst.sse.core.internal.util.Debug;
+import org.eclipse.wst.sse.core.internal.util.Utilities;
+import org.eclipse.wst.sse.ui.internal.util.Assert;
+
+
+/**
+ * Adapts IStructuredDocument events and methods to StyledTextContent events
+ * and methods
+ */
+public class StructuredDocumentToTextAdapter implements IDocumentAdapter, IDocumentAdapterExtension {
+
+ private class DocumentClone extends AbstractDocument {
+
+
+ /**
+ * Creates a new document clone with the given content.
+ *
+ * @param content
+ * the content
+ * @param lineDelimiters
+ * the line delimiters
+ */
+ public DocumentClone(String content, String[] lineDelimiters) {
+ super();
+ setTextStore(new StringTextStore(content));
+ ConfigurableLineTracker tracker = new ConfigurableLineTracker(lineDelimiters);
+ setLineTracker(tracker);
+ getTracker().set(content);
+ completeInitialization();
+ }
+ }
+
+ // A pre-notification listener for the viewer's Document
+ class DocumentListener implements IDocumentListener {
+ protected boolean allTextChanged = false;
+
+ protected DocumentEvent currentEvent;
+
+ synchronized public void documentAboutToBeChanged(DocumentEvent event) {
+ if (isStoppedForwardingChanges())
+ return;
+
+ pendingDocumentChangedEvent = true;
+ allTextChanged = event.getOffset() <= 0 && event.getLength() >= StructuredDocumentToTextAdapter.this.getDocument().getLength();
+ currentEvent = event;
+
+ StructuredDocumentToTextAdapter.this.relayTextChanging(event.getOffset(), event.getLength(), event.getText());
+ }
+
+ synchronized public void documentChanged(DocumentEvent event) {
+ if (isStoppedForwardingChanges())
+ return;
+
+ if (currentEvent != null && event == currentEvent) {
+ if (allTextChanged) {
+ StructuredDocumentToTextAdapter.this.relayTextSet();
+ }
+ else {
+ // temp work around for immediate thread
+ // problem.
+ // should have more general solution
+ // soon. 'syncExec' are rumored to be
+ // prone to hang.
+ StructuredDocumentToTextAdapter.this.relayTextChanged();
+ }
+ }
+
+ currentEvent = null;
+ pendingDocumentChangedEvent = false;
+ handlePendingEvents();
+ lastEvent = null;
+
+ }
+ }
+
+ private static class StringTextStore implements ITextStore {
+
+ private String fContent;
+
+ /**
+ * Creates a new string text store with the given content.
+ *
+ * @param content
+ * the content
+ */
+ public StringTextStore(String content) {
+ Assert.isNotNull(content, "content can not be null when setting text store"); //$NON-NLS-1$
+ fContent = content;
+ }
+
+ /*
+ * @see org.eclipse.jface.text.ITextStore#get(int)
+ */
+ public char get(int offset) {
+ return fContent.charAt(offset);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.ITextStore#get(int, int)
+ */
+ public String get(int offset, int length) {
+ return fContent.substring(offset, offset + length);
+ }
+
+ /*
+ * @see org.eclipse.jface.text.ITextStore#getLength()
+ */
+ public int getLength() {
+ return fContent.length();
+ }
+
+ /*
+ * @see org.eclipse.jface.text.ITextStore#replace(int, int,
+ * java.lang.String)
+ */
+ public void replace(int offset, int length, String text) {
+ }
+
+ /*
+ * @see org.eclipse.jface.text.ITextStore#set(java.lang.String)
+ */
+ public void set(String text) {
+ }
+
+ }
+
+ /**
+ * Changes to the Document/IStructuredDocument can extend beyond the text
+ * change area and require more redrawing to keep the hilighting correct.
+ * The event must be saved so that the redraw is only sent after a
+ * textChanged event is received.
+ */
+ class StructuredDocumentListener implements IStructuredDocumentListener {
+
+ public void newModel(NewDocumentEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges()) {
+ // if
+ // (StructuredDocumentToTextAdapter.this.fStopRelayingChanges)
+ // {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("skipped relaying StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ return;
+ }
+ // should use textSet when all contents have
+ // changed
+ // otherwise need to use the pair of
+ // textChanging and
+ // textChanged.
+ StructuredDocumentToTextAdapter.this.lastEvent = structuredDocumentEvent;
+ }
+
+ public void noChange(final NoChangeEvent structuredDocumentEvent) {
+
+ if (Debug.debugStructuredDocument) {
+ System.out.println("skipped relaying StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ if (structuredDocumentEvent.reason == NoChangeEvent.READ_ONLY_STATE_CHANGE) {
+ if (pendingDocumentChangedEvent) {
+ if (lastEventQueue == null) {
+ lastEventQueue = new ArrayList();
+ }
+ lastEventQueue.add(structuredDocumentEvent);
+ }
+ else {
+ StructuredDocumentToTextAdapter.this.lastEvent = structuredDocumentEvent;
+ }
+ }
+ }
+
+ public void nodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges()) {
+ // if
+ // (StructuredDocumentToTextAdapter.this.fStopRelayingChanges)
+ // {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("not relaying StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ return;
+ }
+ if (Debug.debugStructuredDocument) {
+ System.out.println("saving StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ StructuredDocumentToTextAdapter.this.lastEvent = structuredDocumentEvent;
+ }
+
+ public void regionChanged(RegionChangedEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges()) {
+ // if
+ // (StructuredDocumentToTextAdapter.this.fStopRelayingChanges)
+ // {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("not relaying StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ return;
+ }
+ if (Debug.debugStructuredDocument) {
+ System.out.println("saving StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ StructuredDocumentToTextAdapter.this.lastEvent = structuredDocumentEvent;
+ }
+
+ public void regionsReplaced(RegionsReplacedEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges()) {
+ // if
+ // (StructuredDocumentToTextAdapter.this.fStopRelayingChanges)
+ // {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("not relaying StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ return;
+ }
+ if (Debug.debugStructuredDocument) {
+ System.out.println("saving StructuredDocumentEvent " + structuredDocumentEvent.getClass().getName()); //$NON-NLS-1$
+ }
+ StructuredDocumentToTextAdapter.this.lastEvent = structuredDocumentEvent;
+ }
+ }
+
+ private static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+ private final static boolean redrawBackground = true;
+
+ /** The visible child document. */
+ private ProjectionDocument fChildDocument;
+
+ /** The master document */
+ private IDocument fDocument;
+ /** The document clone for the non-forwarding case. */
+ private IDocument fDocumentClone;
+
+ // only use this temp work around if on GTK
+ // it causes funny "cursor blinking" if used on windows
+ private final boolean forceRedrawOnRegionChanged = Platform.getWS().equals("gtk"); //$NON-NLS-1$
+ /** The original content */
+ private String fOriginalContent;
+ /** The original line delimiters */
+ private String[] fOriginalLineDelimiters;
+
+ private int fStopRelayingChangesRequests = 0;
+
+ private StyledText fStyledTextWidget;
+
+ /** The registered text changed listeners */
+ TextChangeListener[] fTextChangeListeners;
+ protected DocumentListener internalDocumentListener;
+
+ // The listeners for relaying DocumentEvents and
+ // requesting repaints
+ // after modification
+ private IStructuredDocumentListener internalStructuredDocumentListener;
+
+ protected StructuredDocumentEvent lastEvent = null;
+ List lastEventQueue;
+ boolean pendingDocumentChangedEvent;
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * TEST ONLY - TEST ONLY - TEST ONLY NOT API use this constructor only for
+ * tests. Creates a new document adapter which is initiallly not connected
+ * to any document.
+ */
+ public StructuredDocumentToTextAdapter() {
+
+ internalStructuredDocumentListener = new StructuredDocumentListener();
+ internalDocumentListener = new DocumentListener();
+ // for testing only
+ // setDocument(getModelManager().createStructuredDocumentFor(ContentTypeIdentifierForXML.ContentTypeID_XML));
+ }
+
+ /**
+ * Creates a new document adapter which is initiallly not connected to any
+ * document.
+ */
+ public StructuredDocumentToTextAdapter(StyledText styledTextWidget) {
+
+ // do not use 'this()' in this case
+ super();
+ internalStructuredDocumentListener = new StructuredDocumentListener();
+ internalDocumentListener = new DocumentListener();
+ fStyledTextWidget = styledTextWidget;
+ }
+
+ private void _setDocument(IDocument newDoc) {
+ if (fDocument instanceof IStructuredDocument) {
+ ((IStructuredDocument) fDocument).removeDocumentChangedListener(internalStructuredDocumentListener);
+ }
+ fDocument = newDoc;
+ if (!isStoppedForwardingChanges()) {
+ fDocumentClone = null;
+ fOriginalContent = getDocument() != null ? getDocument().get() : null;
+ fOriginalLineDelimiters = getDocument() != null ? getDocument().getLegalLineDelimiters() : null;
+ }
+
+ if (DEBUG && fDocument != null && !(fDocument instanceof ILockable)) {
+
+ System.out.println("Warning: non ILockable document used in StructuredDocumentToTextAdapter"); //$NON-NLS-1$
+ System.out.println(" document updates on non-display thread will not be safe if editor open"); //$NON-NLS-1$
+ }
+ if (fDocument instanceof IStructuredDocument) {
+ ((IStructuredDocument) fDocument).addDocumentChangedListener(internalStructuredDocumentListener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.swt.custom.StyledTextContent#addTextChangeListener(org.eclipse.swt.custom.TextChangeListener)
+ */
+ public synchronized void addTextChangeListener(TextChangeListener listener) {
+
+ // make sure listener is not already in listening
+ // (and if it is, print a warning to aid debugging,
+ // if needed)
+
+ if (Utilities.contains(fTextChangeListeners, listener)) {
+ if (Debug.displayWarnings) {
+ System.out.println("StructuredDocumentToTextAdapter::addTextChangedListeners. listener " + listener + " was added more than once. "); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ else {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("StructuredDocumentToTextAdapter::addTextChangedListeners. Adding an instance of " + listener.getClass() + " as a listener on text adapter."); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ int oldSize = 0;
+ if (fTextChangeListeners != null) {
+ // normally won't be null, but we need to be
+ // sure, for first time through
+ oldSize = fTextChangeListeners.length;
+ }
+ int newSize = oldSize + 1;
+ TextChangeListener[] newListeners = new TextChangeListener[newSize];
+ if (fTextChangeListeners != null) {
+ System.arraycopy(fTextChangeListeners, 0, newListeners, 0, oldSize);
+ }
+ // add listener to last position
+ newListeners[newSize - 1] = listener;
+ //
+ // now switch new for old
+ fTextChangeListeners = newListeners;
+ //
+ }
+ }
+
+ /*
+ * @see org.eclipse.swt.custom.StyledTextContent#getCharCount()
+ */
+ public int getCharCount() {
+
+ // getDocument can sometimes be null during startup
+ // and dispose
+ int result = 0;
+ IDocument doc = getDocument();
+ if (doc != null) {
+ result = getSafeDocument().getLength();
+ }
+ return result;
+ }
+
+ private IDocument getClonedDocument() {
+ if (fDocumentClone == null) {
+ String content = fOriginalContent == null ? "" : fOriginalContent; //$NON-NLS-1$
+ String[] delims = fOriginalLineDelimiters == null ? DefaultLineTracker.DELIMITERS : fOriginalLineDelimiters;
+ fDocumentClone = new DocumentClone(content, delims);
+ }
+ return fDocumentClone;
+ }
+
+ Display getDisplay() {
+
+ // Note: the workbench should always have a display
+ // (unless running headless), whereas Display.getCurrent()
+ // only returns the display if the currently executing thread
+ // has one.
+ if (PlatformUI.isWorkbenchRunning())
+ return PlatformUI.getWorkbench().getDisplay();
+ else
+ return null;
+ }
+
+ /**
+ * Returns the visible document.
+ *
+ * @return IDocument
+ */
+ protected IDocument getDocument() {
+
+ if (fChildDocument == null)
+ return fDocument;
+ return fChildDocument;
+ }
+
+ /**
+ * Returns region in master document of given region (should be region in
+ * projection document)
+ *
+ * @return region if no projection document exists, region of master
+ * document if possible, null otherwise
+ */
+ private IRegion getProjectionToMasterRegion(IRegion region) {
+ IRegion originalRegion = region;
+ if (fChildDocument != null) {
+ try {
+ originalRegion = fChildDocument.getProjectionMapping().toOriginRegion(region);
+ }
+ catch (BadLocationException e) {
+ Logger.logException(e);
+ }
+ }
+
+ return originalRegion;
+ }
+
+ /**
+ * Returns offset in projection document of given offset (should be offset
+ * in master document)
+ *
+ * @return offset if no projection document exists, offset of projection
+ * document if possible, -1 otherwise
+ */
+ private int getMasterToProjectionOffset(int offset) {
+ int originalOffset = offset;
+ if (fChildDocument != null) {
+ try {
+ originalOffset = fChildDocument.getProjectionMapping().toImageOffset(offset);
+ }
+ catch (BadLocationException e) {
+ Logger.logException(e);
+ }
+ }
+
+ return originalOffset;
+ }
+
+ /**
+ * Return the line at the given character offset without delimiters.
+ * <p>
+ *
+ * @param offset
+ * offset of the line to return. Does not include delimiters of
+ * preceeding lines. Offset 0 is the first character of the
+ * document.
+ * @return the line text without delimiters
+ */
+ public java.lang.String getLine(int lineNumber) {
+
+ String result = null;
+ if (lineNumber >= getLineCount()) {
+ if (Debug.displayWarnings) {
+ System.out.println("Development Debug: IStructuredDocument:getLine() error. lineNumber requested (" + lineNumber + ") was greater than number of lines(" + getLineCount() + "). EmptyString returned"); //$NON-NLS-1$//$NON-NLS-3$//$NON-NLS-2$
+ }
+ result = EMPTY_STRING;
+ }
+ else {
+ IDocument doc = getSafeDocument();
+ if (doc == null) {
+ result = EMPTY_STRING;
+ }
+ else {
+ try {
+ IRegion r = doc.getLineInformation(lineNumber);
+ if (r.getLength() > 0) {
+ result = doc.get(r.getOffset(), r.getLength());
+ }
+ else {
+ result = EMPTY_STRING;
+ }
+ }
+ catch (BadLocationException e) {
+ result = EMPTY_STRING;
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Tries to repair the line information.
+ *
+ * @param document
+ * the document
+ * @see IRepairableDocument#repairLineInformation()
+ * @see Eclipse 3.0
+ */
+ private void repairLineInformation(IDocument document) {
+ if (document instanceof IRepairableDocument) {
+ IRepairableDocument repairable = (IRepairableDocument) document;
+ repairable.repairLineInformation();
+ }
+ }
+
+ /**
+ * Return the line index at the given character offset.
+ * <p>
+ *
+ * @param offset
+ * offset of the line to return. The first character of the
+ * document is at offset 0. An offset of getLength() is valid
+ * and should answer the number of lines.
+ * @return the line index. The first line is at index 0. If the character
+ * at offset is a delimiter character, answer the line index of
+ * the line that is delimited. For example, text = "\r\n\r\n",
+ * delimiter = "\r\n", then: getLineAtOffset(0) == 0
+ * getLineAtOffset(1) == 0 getLineAtOffset(2) == 1
+ * getLineAtOffset(3) == 1 getLineAtOffset(4) == 2
+ */
+ public int getLineAtOffset(int offset) {
+
+ int result = 0;
+ IDocument doc = getSafeDocument();
+ if (doc != null) {
+ try {
+ result = doc.getLineOfOffset(offset);
+ }
+ catch (BadLocationException x) {
+ repairLineInformation(doc);
+ try {
+ result = doc.getLineOfOffset(offset);
+ }
+ catch (BadLocationException x2) {
+ // should not occur, but seems to for projection
+ // documents, related to repainting overview ruler
+ result = 0;
+ }
+ }
+ }
+ return result;
+ }
+
+ public int getLineCount() {
+ int result = 0;
+ IDocument doc = getSafeDocument();
+ if (doc != null) {
+ result = doc.getNumberOfLines();
+ }
+ return result;
+ }
+
+ /*
+ * @see org.eclipse.swt.custom.StyledTextContent#getLineDelimiter
+ */
+ public String getLineDelimiter() {
+ String result = null;
+ if (getParentDocument() instanceof IStructuredDocument) {
+ result = ((IStructuredDocument) getParentDocument()).getLineDelimiter();
+ }
+ else {
+ IDocument doc = getSafeDocument();
+ result = TextUtilities.getDefaultLineDelimiter(doc);
+ }
+ return result;
+ }
+
+ /**
+ * Return the character offset of the first character of the given line.
+ * <p>
+ *
+ * @param lineIndex
+ * index of the line. The first line is at index 0.
+ * @return offset offset of the first character of the line. The first
+ * character of the document is at offset 0. The return value
+ * should include line delimiters. For example, text =
+ * "\r\ntest\r\n", delimiter = "\r\n", then: getOffsetAtLine(0) ==
+ * 0 getOffsetAtLine(1) == 2 getOffsetAtLine(2) == 8 NOTE: When
+ * there is no text (i.e., no lines), getOffsetAtLine(0) is a
+ * valid call that should return 0.
+ */
+ public int getOffsetAtLine(int lineIndex) {
+
+ int result = 0;
+ IDocument doc = getSafeDocument();
+ if (doc != null) {
+ try {
+ result = doc.getLineOffset(lineIndex);
+ }
+ catch (BadLocationException e) {
+ result = 0;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns the parent document
+ *
+ * @return the parent document
+ */
+ private IDocument getParentDocument() {
+ return fDocument;
+ }
+
+ /**
+ * This is the document to use for request from the StyledText widget. Its
+ * either the live documnet or a clone of it, depending on stop/resume
+ * state.
+ */
+ private IDocument getSafeDocument() {
+ IDocument result = null;
+ if (isStoppedForwardingChanges()) {
+ result = getClonedDocument();
+ }
+ else {
+ // note, this document can be normal structured text document,
+ // or the projection/child document
+ result = getDocument();
+ }
+ return result;
+ }
+
+ /**
+ * @return org.eclipse.swt.custom.StyledText
+ */
+ StyledText getStyledTextWidget() {
+ return fStyledTextWidget;
+ }
+
+ /**
+ * Returns a string representing the content at the given range.
+ * <p>
+ *
+ * @param start
+ * the start offset of the text to return. Offset 0 is the
+ * first character of the document.
+ * @param length
+ * the length of the text to return
+ * @return the text at the given range
+ */
+ public String getTextRange(int start, int length) {
+ String result = null;
+ try {
+ IDocument doc = getSafeDocument();
+ result = doc.get(start, length);
+ }
+ catch (BadLocationException e) {
+ result = EMPTY_STRING;
+ }
+ return result;
+ }
+
+ /**
+ * assume only for "no change" events, for now
+ */
+ protected void handlePendingEvents() {
+
+ if (lastEventQueue == null)
+ return;
+
+ Iterator iterator = lastEventQueue.iterator();
+ while (iterator.hasNext()) {
+ NoChangeEvent noChangeEvent = (NoChangeEvent) iterator.next();
+ redrawNoChange(noChangeEvent);
+ }
+
+ lastEventQueue = null;
+ lastEvent = null;
+ }
+
+ boolean isStoppedForwardingChanges() {
+ return fStopRelayingChangesRequests > 0;
+ }
+
+ /**
+ * this method is assumed to be called only for read only region changes.
+ */
+ protected void redrawNoChange(NoChangeEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges())
+ return;
+ if (Debug.debugStructuredDocument) {
+ System.out.println("maybe redraw stuff"); //$NON-NLS-1$
+ }
+
+ int startOffset = structuredDocumentEvent.getOffset();
+ int length = structuredDocumentEvent.getLength();
+ redrawRangeWithLength(startOffset, length);
+
+ }
+
+ /**
+ * Request a redraw of the text range occupied by the given
+ * StructuredDocumentRegionsReplacedEvent
+ *
+ * @param structuredDocumentEvent
+ */
+ protected void redrawNodesReplaced(StructuredDocumentRegionsReplacedEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges())
+ return;
+ if (Debug.debugStructuredDocument) {
+ System.out.println("maybe redraw stuff"); //$NON-NLS-1$
+ }
+ // just the new stuff
+ IStructuredDocumentRegionList newStructuredDocumentRegions = structuredDocumentEvent.getNewStructuredDocumentRegions();
+
+ int nNewNodes = newStructuredDocumentRegions.getLength();
+ if (nNewNodes > 0) {
+ IStructuredDocumentRegion firstNode = newStructuredDocumentRegions.item(0);
+ IStructuredDocumentRegion lastNode = newStructuredDocumentRegions.item(nNewNodes - 1);
+ redrawRange(firstNode.getStartOffset(), lastNode.getEndOffset());
+ }
+ }
+
+ /**
+ * Redraws the give offsets in terms of the StructuredDocument. If only
+ * part of the model is visible, ensures that only the visible portion of
+ * the given range is redrawn.
+ *
+ * @param startModelOffset
+ * @param endModelOffset
+ */
+ private void redrawRange(final int startModelOffset, final int endModelOffset) {
+
+ if (getDocument() == null)
+ return;
+ if (Debug.debugStructuredDocument) {
+ System.out.println("redraw stuff: " + startModelOffset + "-" + endModelOffset); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (fChildDocument == null) {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ getStyledTextWidget().redrawRange(startModelOffset, endModelOffset - startModelOffset, redrawBackground);
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+
+ }
+ else {
+ int high = getDocument().getLength();
+ int startOffset = getMasterToProjectionOffset(startModelOffset);
+
+ int endOffset = getMasterToProjectionOffset(endModelOffset);
+
+ // if offsets were not visible, just try to redraw everything in
+ // the child document
+ // // not visible
+ // if (endOffset < 0 || startOffset > high)
+ // return;
+ // restrict lower bound
+ if (startOffset < 0) {
+ startOffset = 0;
+ }
+ // restrict upper bound
+ // if (endOffset > high) {
+ // endOffset = high;
+ // }
+ if (endOffset < 0) {
+ endOffset = high;
+ }
+
+ int length = endOffset - startOffset;
+ // redrawBackground with false would be faster
+ // but assumes background (or font) is not
+ // changing
+ final int finalStartOffset = startOffset;
+ final int finallength = length;
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+ getStyledTextWidget().redrawRange(finalStartOffset, finallength, redrawBackground);
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+
+ }
+ }
+
+ /**
+ * Redraws the give offsets in terms of the Flat Node model. If only part
+ * of the model is visible, ensures that only the visible portion of the
+ * given range is redrawn.
+ *
+ * @param startModelOffset
+ * @param endModelOffset
+ */
+ private void redrawRangeWithLength(final int startModelOffset, final int length) {
+
+ if (getDocument() == null)
+ return;
+ if (Debug.debugStructuredDocument) {
+ System.out.println("redraw stuff: " + startModelOffset + "-" + length); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ if (fChildDocument == null) {
+ Runnable runnable = new Runnable() {
+ public void run() {
+ getStyledTextWidget().redrawRange(startModelOffset, length, redrawBackground);
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+ }
+ else {
+ int high = getDocument().getLength();
+ // TODO need to take into account segmented visible regions
+ int startOffset = getMasterToProjectionOffset(startModelOffset);
+ // not visible
+ if (startOffset > high || length < 1)
+ return;
+ // restrict lower bound
+ if (startOffset < 0) {
+ startOffset = 0;
+ }
+ int endOffset = startOffset + length - 1;
+ // restrict upper bound
+ if (endOffset > high) {
+ endOffset = high;
+ }
+
+ // note: length of the child documnet should be
+ // updated,
+ // need to investigate why its not at this
+ // point, but is
+ // probably just because the document event
+ // handling is not
+ // completely finished.
+ int newLength = endOffset - startOffset; // d283007
+
+ // redrawBackground with false would be faster
+ // but assumes background (or font) is not
+ // changing
+ final int finalStartOffset = startOffset;
+ final int finalNewLength = newLength;
+ Runnable runnable = new Runnable() {
+ public void run() {
+ getStyledTextWidget().redrawRange(finalStartOffset, finalNewLength, redrawBackground);
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+ }
+ }
+
+ /**
+ * Request a redraw of the text range occupied by the given
+ * RegionChangedEvent for certain (not all) ITextRegion contexts
+ *
+ * @param structuredDocumentEvent
+ */
+ protected void redrawRegionChanged(RegionChangedEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges()) {
+ return;
+ }
+ if (Debug.debugStructuredDocument) {
+ System.out.println("maybe redraw stuff"); //$NON-NLS-1$
+ }
+
+
+ // (nsd) TODO: try to make this reliable somehow
+ // without being directly content dependent
+ // if ((region instanceof ITextRegionContainer) ||
+ // (type == XMLJSPRegionContexts.BLOCK_TEXT) ||
+ // (type == XMLJSPRegionContexts.JSP_CONTENT)) {
+ // IStructuredDocumentRegion flatNode =
+ // structuredDocumentEvent.getStructuredDocumentRegion();
+ // // redraw background of false is faster,
+ // // but assumes background (or font) is not
+ // changing
+ // redrawRange(flatNode.getStartOffset(region),
+ // flatNode.getEndOffset(region));
+ // }
+ if (forceRedrawOnRegionChanged) {
+ // workaround for redrawing problems on Linux-GTK
+ int startOffset = structuredDocumentEvent.getOffset();
+ int endOffset = structuredDocumentEvent.getOffset() + structuredDocumentEvent.getLength();
+ try {
+ IRegion startLine = structuredDocumentEvent.fDocument.getLineInformationOfOffset(startOffset);
+ IRegion endLine = structuredDocumentEvent.fDocument.getLineInformationOfOffset(endOffset);
+ if (startLine != null && endLine != null) {
+ redrawRange(startLine.getOffset(), endLine.getOffset() + endLine.getLength());
+ }
+ }
+ catch (BadLocationException e) {
+ // nothing for now
+ }
+ }
+ }
+
+ /**
+ * Request a redraw of the text range occupied by the given
+ * RegionsReplacedEvent
+ *
+ * @param structuredDocumentEvent
+ */
+ protected void redrawRegionsReplaced(RegionsReplacedEvent structuredDocumentEvent) {
+
+ if (isStoppedForwardingChanges())
+ return;
+ if (Debug.debugStructuredDocument) {
+ System.out.println("maybe redraw stuff"); //$NON-NLS-1$
+ }
+ ITextRegionList newRegions = structuredDocumentEvent.getNewRegions();
+ int nRegions = newRegions.size();
+ if (nRegions > 0) {
+ ITextRegion firstRegion = newRegions.get(0);
+ ITextRegion lastRegion = newRegions.get(nRegions - 1);
+ IStructuredDocumentRegion flatNode = structuredDocumentEvent.getStructuredDocumentRegion();
+ redrawRange(flatNode.getStartOffset(firstRegion), flatNode.getEndOffset(lastRegion));
+ }
+ }
+
+ protected void redrawTextChanged() {
+
+ if (lastEvent != null) {
+ // update display, since some cases can effect
+ // highlighting beyond the changed text area.
+ if (lastEvent instanceof StructuredDocumentRegionsReplacedEvent)
+ redrawNodesReplaced((StructuredDocumentRegionsReplacedEvent) lastEvent);
+ if (lastEvent instanceof RegionsReplacedEvent)
+ redrawRegionsReplaced((RegionsReplacedEvent) lastEvent);
+ if (lastEvent instanceof RegionChangedEvent)
+ redrawRegionChanged((RegionChangedEvent) lastEvent);
+ // moved following line to 'document changed' so
+ // the "last event" can be
+ // re-drawn after pending re-draws
+ // lastEvent = null;
+ }
+ }
+
+ /**
+ * Sends a text replace event to all registered listeners.
+ */
+ protected void relayTextChanged() {
+
+ if (isStoppedForwardingChanges()) {
+ if (Debug.debugStructuredDocument && getDocument() != null) {
+ System.out.println("NOT relaying text changed (" + getDocument().getLength() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return;
+ }
+ if (Debug.debugStructuredDocument && getDocument() != null) {
+ System.out.println("relaying text changed (" + getDocument().getLength() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ final TextChangedEvent textChangedEvent = new TextChangedEvent(this);
+
+ // we must assign listeners to local variable, since
+ // the add and remove listener
+ // methods can change the actual instance of the
+ // listener array from another thread
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+ if (fTextChangeListeners != null) {
+ Object[] holdListeners = fTextChangeListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ // this is a safe cast, since addListeners
+ // requires a IStructuredDocumentListener
+ ((TextChangeListener) holdListeners[i]).textChanged(textChangedEvent);
+ }
+ }
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+ redrawTextChanged();
+ }
+
+ /**
+ * Sends a text change to all registered listeners
+ */
+ protected void relayTextChanging(int requestedStart, int requestedLength, String requestedChange) {
+
+ if (getDocument() == null)
+ return;
+ if (isStoppedForwardingChanges()) {
+ if (Debug.debugStructuredDocument && getDocument() != null) {
+ System.out.println("NOT relaying text changing: " + requestedStart + ":" + getDocument().getLength()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return;
+ }
+ if (Debug.debugStructuredDocument && getDocument() != null) {
+ System.out.println("relaying text changing: " + requestedStart + ":" + getDocument().getLength()); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ lastEvent = null;
+ try {
+ final TextChangingEvent textChangingEvent = new TextChangingEvent(this);
+
+ textChangingEvent.start = requestedStart;
+ textChangingEvent.replaceCharCount = requestedLength;
+ textChangingEvent.newCharCount = (requestedChange == null ? 0 : requestedChange.length());
+ textChangingEvent.replaceLineCount = getDocument().getNumberOfLines(requestedStart, requestedLength) - 1;
+ textChangingEvent.newText = requestedChange;
+ textChangingEvent.newLineCount = (requestedChange == null ? 0 : getDocument().computeNumberOfLines(requestedChange));
+
+ // we must assign listeners to local variable,
+ // since the add and remove listner
+ // methods can change the actual instance of the
+ // listener array from another thread
+ Runnable runnable = new Runnable() {
+ public void run() {
+ if (fTextChangeListeners != null) {
+ TextChangeListener[] holdListeners = fTextChangeListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ // this is a safe cast, since
+ // addListeners requires a
+ // IStructuredDocumentListener
+ holdListeners[i].textChanging(textChangingEvent);
+ }
+ }
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+ }
+ catch (BadLocationException e) {
+ // log for now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ }
+
+ /**
+ * Sends a text set event to all registered listeners. Widget should
+ * redraw itself automatically.
+ */
+ protected void relayTextSet() {
+
+ if (isStoppedForwardingChanges()) {
+ if (Debug.debugStructuredDocument && getDocument() != null) {
+ System.out.println("NOT relaying text set (" + getDocument().getLength() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ return;
+ }
+ if (Debug.debugStructuredDocument && getDocument() != null) {
+ System.out.println("relaying text set (" + getDocument().getLength() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ lastEvent = null;
+ final TextChangedEvent textChangedEvent = new TextChangedEvent(this);
+
+ // we must assign listeners to local variable, since
+ // the add and remove listner
+ // methods can change the actual instance of the
+ // listener array from another thread
+ Runnable runnable = new Runnable() {
+ public void run() {
+ if (fTextChangeListeners != null) {
+ TextChangeListener[] holdListeners = fTextChangeListeners;
+ for (int i = 0; i < holdListeners.length; i++) {
+ holdListeners[i].textSet(textChangedEvent);
+ }
+ }
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.eclipse.swt.custom.StyledTextContent#removeTextChangeListener(org.eclipse.swt.custom.TextChangeListener)
+ */
+ public synchronized void removeTextChangeListener(final TextChangeListener listener) {
+
+ if ((fTextChangeListeners != null) && (listener != null)) {
+ // if its not in the listeners, we'll ignore the
+ // request
+ if (!Utilities.contains(fTextChangeListeners, listener)) {
+ if (Debug.displayWarnings) {
+ System.out.println("StructuredDocumentToTextAdapter::removeTextChangedListeners. listener " + listener + " was not present. "); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ }
+ else {
+ if (Debug.debugStructuredDocument) {
+ System.out.println("StructuredDocumentToTextAdapter::addTextChangedListeners. Removing an instance of " + listener.getClass() + " as a listener on text adapter."); //$NON-NLS-2$//$NON-NLS-1$
+ }
+ final int oldSize = fTextChangeListeners.length;
+ int newSize = oldSize - 1;
+ final TextChangeListener[] newListeners = new TextChangeListener[newSize];
+
+ Runnable runnable = new Runnable() {
+ public void run() {
+ int index = 0;
+ for (int i = 0; i < oldSize; i++) {
+ if (fTextChangeListeners[i] != listener) {
+ // copy old to new if its not the
+ // one we are removing
+ newListeners[index++] = fTextChangeListeners[i];
+ }
+ }
+ }
+ };
+ runOnDisplayThreadIfNeedede(runnable);
+ // now that we have a new array, let's
+ // switch it for the old one
+ fTextChangeListeners = newListeners;
+ }
+ }
+ }
+
+ /**
+ * Replace the text with "newText" starting at position "start" for a
+ * length of "replaceLength".
+ * <p>
+ * Implementors have to notify TextChanged listeners after the content has
+ * been updated. The TextChangedEvent should be set as follows:
+ * <ul>
+ * <li>event.type = SWT.TextReplaced
+ * <li>event.start = start of the replaced text
+ * <li>event.numReplacedLines = number of replaced lines
+ * <li>event.numNewLines = number of new lines
+ * <li>event.replacedLength = length of the replaced text
+ * <li>event.newLength = length of the new text
+ * </ul>
+ * <b>NOTE: </b> numNewLines is the number of inserted lines and
+ * numReplacedLines is the number of deleted lines based on the change
+ * that occurs visually. For example:
+ * <ul>
+ * <li>(replacedText, newText) ==> (numReplacedLines, numNewLines)
+ * <li>("", "\n") ==> (0, 1)
+ * <li>("\n\n", "a") ==> (2, 0)
+ * <li>("a", "\n\n") ==> (0, 2)
+ * <li>("\n", "") ==> (1, 0)
+ * </ul>
+ * </p>
+ *
+ * @param start
+ * start offset of text to replace, none of the offsets include
+ * delimiters of preceeding lines, offset 0 is the first
+ * character of the document
+ * @param replaceLength
+ * start offset of text to replace
+ * @param newText
+ * start offset of text to replace
+ */
+ public void replaceTextRange(int start, int replaceLength, String text) {
+
+ if (getParentDocument() instanceof IStructuredDocument) {
+ // the structuredDocument initiates the "changing"
+ // and "changed" events.
+ // they are both fired by the time this method
+ // returns.
+ IRegion region = getProjectionToMasterRegion(new Region(start, replaceLength));
+ if (region != null) {
+ ((IStructuredDocument) getParentDocument()).replaceText(this, region.getOffset(), region.getLength(), text);
+ return;
+ }
+ }
+ // default is to just try and replace text range in current document
+ try {
+ getDocument().replace(start, replaceLength, text);
+ }
+ catch (BadLocationException e) {
+ // log for now, unless we find reason not to
+ Logger.log(Logger.INFO, e.getMessage());
+ }
+ }
+
+ /**
+ * @see org.eclipse.jface.text.IDocumentAdapterExtension#resumeForwardingDocumentChanges()
+ */
+ public void resumeForwardingDocumentChanges() {
+
+ // from re-reading the textSet API in StyledText, we
+ // must call
+ // textSet if all the contents changed. If all the
+ // contents did
+ // not change, we need to call the pair of APIs,
+ // textChanging and
+ // textChanged. So, if we ever keep careful track of
+ // changes
+ // during stop forwarding and resume forwarding, we
+ // can
+ // investigate change make use of the pair of APIs.
+ fStopRelayingChangesRequests--;
+ if (fStopRelayingChangesRequests == 0) {
+ // fIsForwarding= true;
+ fDocumentClone = null;
+ fOriginalContent = null;
+ fOriginalLineDelimiters = null;
+ // fireTextSet();
+ relayTextSet();
+ }
+ }
+
+ /**
+ * This 'Runnable' should be very brief, and should not "call out" to
+ * other code which itself might call syncExec, or deadlock might occur.
+ *
+ * @param r
+ */
+ private void runOnDisplayThreadIfNeedede(Runnable r) {
+ // if there is no Display at all (that is, running headless),
+ // or if we are already running on the display thread, then
+ // simply execute the runnable.
+ if (getDisplay() == null || (Thread.currentThread() == getDisplay().getThread())) {
+ r.run();
+ }
+ else {
+ // otherwise force the runnable to run on the display thread.
+ //
+ // Its unclear if we need this at all, once
+ // we "force" document update to always take place on display
+ // thread.
+ IDocument doc = getDocument();
+ if (doc instanceof ILockable) {
+
+ ILock lock = null;
+ try {
+ lock = ((ILockable) doc).getLockObject();
+ lock.acquire();
+ getDisplay().syncExec(r);
+ }
+ finally {
+ if (lock != null) {
+ lock.release();
+ }
+ }
+ }
+ else {
+ // else, ignore!, since risk of deadlock
+ throw new IllegalStateException("non lockable document used for structuredDocumentToTextAdapter"); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /**
+ * @param newModel
+ */
+ public void setDocument(IDocument document) {
+
+ if (getDocument() != null) {
+ getDocument().removePrenotifiedDocumentListener(internalDocumentListener);
+ }
+ lastEvent = null;
+ if (document instanceof ProjectionDocument) {
+ fChildDocument = (ProjectionDocument) document;
+ _setDocument(fChildDocument.getMasterDocument());
+ }
+ else {
+ fChildDocument = null;
+ _setDocument(document);
+ }
+ if (getDocument() != null) {
+ getDocument().addPrenotifiedDocumentListener(internalDocumentListener);
+ }
+ }
+
+ /**
+ * @see IDocument#setText
+ */
+ public void setText(String string) {
+
+ if (isStoppedForwardingChanges()) {
+ fDocumentClone = null;
+ fOriginalContent = getDocument().get();
+ fOriginalLineDelimiters = getDocument().getLegalLineDelimiters();
+ }
+ else if (getParentDocument() instanceof IStructuredDocument) {
+ ((IStructuredDocument) getDocument()).setText(this, string);
+ }
+ else {
+ getDocument().set(string);
+ }
+ relayTextSet();
+ }
+
+ /**
+ * This method was added to make testing easier. Normally, the widget is
+ * specified on the constructor.
+ */
+ public void setWidget(StyledText widget) {
+
+ fStyledTextWidget = widget;
+ }
+
+ /**
+ * @see org.eclipse.jface.text.IDocumentAdapterExtension#stopForwardingDocumentChanges()
+ */
+ public void stopForwardingDocumentChanges() {
+
+ fStopRelayingChangesRequests++;
+ // only need to take snapshot on first request
+ if (fStopRelayingChangesRequests == 1) {
+ fDocumentClone = null;
+ fOriginalContent = getDocument().get();
+ fOriginalLineDelimiters = getDocument().getLegalLineDelimiters();
+ }
+ }
+}

Back to the top