| /******************************************************************************* |
| * 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(); |
| } |
| } |
| } |