blob: 4a276fd59130f08fb75841b1a2647699e3e5540f [file] [log] [blame]
/*******************************************************************************
* 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();
}
}
}