blob: f1d7c839f8fee1fb2162cce3ee0c0bd287831aec [file] [log] [blame]
package org.eclipse.jface.text.reconciler;
/*
* (c) Copyright IBM Corp. 2000, 2001.
* All Rights Reserved.
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentListener;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextInputListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TypedRegion;
import org.eclipse.jface.util.Assert;
/**
* Abstract implementation of <code>IReconciler</code>. The reconciler
* listens to input document changes as well as changes of
* the input document of the text viewer it is installed on. Depending on
* its configuration it manages the received change notifications in a
* queue folding neighboring or overlapping changes together. After the
* configured period of time, it processes one dirty region after the other.
* A reconciler is started using its <code>install</code> method. It is the
* clients responsibility to stop a reconciler using its <code>uninstall</code>
* method. Unstopped reconcilers do not free their resources.<p>
* It is subclass responsibility to specify how dirty regions are processed.
*
* @see IReconciler
* @see IDocumentListener
* @see ITextInputListener
* @see DirtyRegion
*/
abstract public class AbstractReconciler implements IReconciler {
/**
* Background thread for the periodic reconciling activity.
*/
class BackgroundThread extends Thread {
/** Has the reconciler been canceled */
private boolean fCanceled= false;
/** Has the reconciler been reset */
private boolean fReset= false;
/** Has a change been applied */
private boolean fIsDirty= false;
/** Is a reconciling strategy active */
private boolean fIsActive= false;
/**
* Creates a new background thread. The thread
* runs with minimal priority.
*
* @param name the thread's name
*/
public BackgroundThread(String name) {
super(name);
setPriority(Thread.MIN_PRIORITY);
setDaemon(true);
}
/**
* Returns whether a reconciling strategy is active right now.
*
* @return <code>true</code> if a activity is active
*/
public boolean isActive() {
return fIsActive;
}
/**
* Cancels the background thread.
*/
public void cancel() {
fCanceled= true;
synchronized (fDirtyRegionQueue) {
fDirtyRegionQueue.notifyAll();
}
}
/**
* Reset the background thread as the text viewer has been changed,
*/
public void reset() {
if (fDelay > 0) {
synchronized (this) {
fIsDirty= true;
fReset= true;
}
} else {
synchronized(this) {
fIsDirty= true;
}
synchronized (fDirtyRegionQueue) {
fDirtyRegionQueue.notifyAll();
}
}
}
/**
* The periodic activity. Wait until there is something in the
* queue managing the changes applied to the text viewer. Remove the
* first change from the queue and process it.
*/
public void run() {
synchronized (fDirtyRegionQueue) {
try {
fDirtyRegionQueue.wait(fDelay);
} catch (InterruptedException x) {
}
}
initialProcess();
while (!fCanceled) {
synchronized (fDirtyRegionQueue) {
try {
fDirtyRegionQueue.wait(fDelay);
} catch (InterruptedException x) {
}
}
if (fCanceled)
break;
if (!fIsDirty)
continue;
if (fReset) {
synchronized (this) {
fReset= false;
}
continue;
}
DirtyRegion r= null;
synchronized (fDirtyRegionQueue) {
r= fDirtyRegionQueue.removeNextDirtyRegion();
}
fIsActive= true;
if (fProgressMonitor != null)
fProgressMonitor.setCanceled(false);
process(r);
synchronized (this) {
fIsDirty= false;
}
fIsActive= false;
}
}
};
/**
* Internal document listener and text input listener.
*/
class Listener implements IDocumentListener, ITextInputListener {
/*
* @see IDocumentListener#documentAboutToBeChanged
*/
public void documentAboutToBeChanged(DocumentEvent e) {
}
/*
* @see IDocumentListener#documentChanged
*/
public void documentChanged(DocumentEvent e) {
if (fProgressMonitor != null && fThread.isActive())
fProgressMonitor.setCanceled(true);
if (fIsIncrementalReconciler)
createDirtyRegion(e);
fThread.reset();
}
/*
* @see ITextInputListener#inputDocumentAboutToBeChanged
*/
public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) {
if (oldInput == fDocument) {
if (fDocument != null)
fDocument.removeDocumentListener(this);
if (fIsIncrementalReconciler) {
fDirtyRegionQueue.purgeQueue();
if (fDocument != null && fDocument.getLength() > 0) {
DocumentEvent e= new DocumentEvent(fDocument, 0, fDocument.getLength(), null);
createDirtyRegion(e);
}
}
fDocument= null;
}
}
/*
* @see ITextInputListener#inputDocumentChanged
*/
public void inputDocumentChanged(IDocument oldInput, IDocument newInput) {
if (newInput == null)
return;
fDocument= newInput;
reconcilerDocumentChanged(fDocument);
fDocument.addDocumentListener(this);
if (fIsIncrementalReconciler) {
DocumentEvent e= new DocumentEvent(fDocument, 0, 0, fDocument.get());
createDirtyRegion(e);
}
if (oldInput == null && !fThread.isAlive())
fThread.start();
else
fThread.reset();
}
};
/** Queue to manage the changes applied to the text viewer */
private DirtyRegionQueue fDirtyRegionQueue;
/** The background thread */
private BackgroundThread fThread;
/** Internal document and text input listener */
private Listener fListener;
/** The background thread delay */
private int fDelay= 500;
/** Are there incremental reconciling strategies? */
private boolean fIsIncrementalReconciler= true;
/** The progress monitor used by this reconciler */
private IProgressMonitor fProgressMonitor;
/** The text viewer's document */
private IDocument fDocument;
/** The text viewer */
private ITextViewer fViewer;
/**
* Processes a dirty region. If the dirty region is <code>null</code> the whole
* document is consider being dirty. The dirty region is partitioned by the
* document and each partition is handed over to a reconciling strategy registered
* for the partition's content type.
*
* @param dirtyRegion the dirty region to be processed
*/
abstract protected void process(DirtyRegion dirtyRegion);
/**
* Hook called when the document whose contents should be reconciled
* has been changed, i.e., the input document of the text viewer this
* reconciler is installed on. Usually, subclasses use this hook to
* inform all their reconciling strategies about the change.
*
* @param newDocument the new reconciler document
*/
abstract protected void reconcilerDocumentChanged(IDocument newDocument);
/**
* Creates a new reconciler with the following configuration: it is
* an incremental reconciler which kicks in every 500 ms. There are no
* predefined reconciling strategies.
*/
protected AbstractReconciler() {
super();
}
/**
* Tells the reconciler how long it should collect text changes before
* it activates the appropriate reconciling strategies.
*
* @param delay the duration in milli seconds of a change collection period.
*/
public void setDelay(int delay) {
fDelay= delay;
}
/**
* Tells the reconciler whether any of the available reconciling strategies
* is interested in getting detailed dirty region information or just in the
* fact the the document has been changed. In the second case, the reconciling
* can not incrementally be pursued.
*
* @param isIncremental indicates whether this reconciler will be configured with
* incremental reconciling strategies
*
* @see DirtyRegion
* @see IReconcilingStrategy
*/
public void setIsIncrementalReconciler(boolean isIncremental) {
fIsIncrementalReconciler= isIncremental;
}
/**
* Sets the progress monitor of this reconciler.
*
* @param monitor the monitor to be used
*/
public void setProgressMonitor(IProgressMonitor monitor) {
fProgressMonitor= monitor;
}
/**
* Returns whether any of the reconciling strategies is interested in
* detailed dirty region information.
*
* @return whether this reconciler is incremental
*
* @see IReconcilingStrategy
*/
protected boolean isIncrementalReconciler() {
return fIsIncrementalReconciler;
}
/**
* Returns the input document of the text viewer this reconciler is installed on.
*
* @return the reconciler document
*/
protected IDocument getDocument() {
return fDocument;
}
/**
* Returns the text viewer this reconciler is installed on.
*
* @return the text viewer this reconciler is installed on
*/
protected ITextViewer getTextViewer() {
return fViewer;
}
/**
* Returns the progress monitor of this reconciler.
*
* @return the progress monitor of this reconciler
*/
protected IProgressMonitor getProgressMonitor() {
return fProgressMonitor;
}
/*
* @see IReconciler#install
*/
public void install(ITextViewer textViewer) {
Assert.isNotNull(textViewer);
fViewer= textViewer;
fListener= new Listener();
fViewer.addTextInputListener(fListener);
fDirtyRegionQueue= new DirtyRegionQueue();
fThread= new BackgroundThread(getClass().getName());
}
/*
* @see IReconciler#uninstall
*/
public void uninstall() {
if (fListener != null) {
fViewer.removeTextInputListener(fListener);
if (fDocument != null) fDocument.removeDocumentListener(fListener);
fListener= null;
fThread.cancel();
fThread= null;
}
}
/**
* Creates a dirty region for a document event and adds it to the queue.
*
* @param e the document event for which to create a dirty region
*/
private void createDirtyRegion(DocumentEvent e) {
if (e.getLength() == 0 && e.getText() != null) {
// Insert
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
} else if (e.getText() == null || e.getText().length() == 0) {
// Remove
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
} else {
// Replace (Remove + Insert)
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getLength(), DirtyRegion.REMOVE, null));
fDirtyRegionQueue.addDirtyRegion(new DirtyRegion(e.getOffset(), e.getText().length(), DirtyRegion.INSERT, e.getText()));
}
}
/**
* This method is called on startup of the background activity. It is called only
* once during the life time of the reconciler. Clients may reimplement this method.
*/
protected void initialProcess() {
}
}