/*******************************************************************************
* Copyright (c) 2006, 2011 Wind River Systems, Inc. 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:
* Anton Leherbauer (Wind River Systems) - initial API and implementation
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.text;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.reconciler.DirtyRegion;
import org.eclipse.jface.text.reconciler.MonoReconciler;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.events.ShellListener;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.index.IIndexChangeEvent;
import org.eclipse.cdt.core.index.IIndexChangeListener;
import org.eclipse.cdt.core.index.IIndexerStateEvent;
import org.eclipse.cdt.core.index.IIndexerStateListener;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ElementChangedEvent;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ICElementDelta;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.IElementChangedListener;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.model.IWorkingCopy;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.IWorkingCopyManager;
/**
* A single strategy C reconciler.
*
* @since 4.0
*/
public class CReconciler extends MonoReconciler {
static class SingletonJob extends Job implements ISchedulingRule {
private Runnable fCode;
SingletonJob(String name, Runnable code) {
super(name);
fCode= code;
setPriority(Job.SHORT);
setRule(this);
setUser(false);
setSystem(true);
}
/*
* @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected IStatus run(IProgressMonitor monitor) {
if (!monitor.isCanceled()) {
fCode.run();
}
return Status.OK_STATUS;
}
/*
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#contains(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
public boolean contains(ISchedulingRule rule) {
return rule == this;
}
/*
* @see org.eclipse.core.runtime.jobs.ISchedulingRule#isConflicting(org.eclipse.core.runtime.jobs.ISchedulingRule)
*/
public boolean isConflicting(ISchedulingRule rule) {
return rule == this;
}
}
/**
* Internal part listener for activating the reconciler.
*/
private class PartListener implements IPartListener2 {
/*
* @see org.eclipse.ui.IPartListener2#partActivated(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partActivated(IWorkbenchPartReference partRef) {
}
/*
* @see org.eclipse.ui.IPartListener2#partBroughtToTop(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partBroughtToTop(IWorkbenchPartReference partRef) {
}
/*
* @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partClosed(IWorkbenchPartReference partRef) {
}
/*
* @see org.eclipse.ui.IPartListener2#partDeactivated(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partDeactivated(IWorkbenchPartReference partRef) {
}
/*
* @see org.eclipse.ui.IPartListener2#partHidden(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partHidden(IWorkbenchPartReference partRef) {
if (partRef.getPart(false) == fTextEditor) {
setEditorActive(false);
}
}
/*
* @see org.eclipse.ui.IPartListener2#partInputChanged(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partInputChanged(IWorkbenchPartReference partRef) {
}
/*
* @see org.eclipse.ui.IPartListener2#partOpened(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partOpened(IWorkbenchPartReference partRef) {
}
/*
* @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference)
*/
public void partVisible(IWorkbenchPartReference partRef) {
if (partRef.getPart(false) == fTextEditor) {
CReconciler.this.scheduleReconciling();
setEditorActive(true);
}
}
}
/**
* Internal Shell activation listener for activating the reconciler.
*/
private class ActivationListener extends ShellAdapter {
private Control fControl;
public ActivationListener(Control control) {
Assert.isNotNull(control);
fControl= control;
}
/*
* @see org.eclipse.swt.events.ShellListener#shellActivated(org.eclipse.swt.events.ShellEvent)
*/
@Override
public void shellActivated(ShellEvent e) {
if (!fControl.isDisposed() && fControl.isVisible()) {
if (hasCModelChanged())
CReconciler.this.scheduleReconciling();
setEditorActive(true);
}
}
/*
* @see org.eclipse.swt.events.ShellListener#shellDeactivated(org.eclipse.swt.events.ShellEvent)
*/
@Override
public void shellDeactivated(ShellEvent e) {
if (!fControl.isDisposed() && fControl.getShell() == e.getSource()) {
setEditorActive(false);
}
}
}
/**
* Internal C element changed listener
*/
private class ElementChangedListener implements IElementChangedListener {
/*
* @see org.eclipse.cdt.core.model.IElementChangedListener#elementChanged(org.eclipse.cdt.core.model.ElementChangedEvent)
*/
public void elementChanged(ElementChangedEvent event) {
if (event.getType() == ElementChangedEvent.POST_CHANGE) {
if (isRelevantDelta(event.getDelta())) {
if (!fIsReconciling && isEditorActive() && fInitialProcessDone) {
CReconciler.this.scheduleReconciling();
} else {
setCModelChanged(true);
}
}
}
}
private boolean isRelevantDelta(ICElementDelta delta) {
final int flags = delta.getFlags();
if ((flags & ICElementDelta.F_CONTENT) != 0) {
if (!fIsReconciling && isRelevantElement(delta.getElement())) {
// mark model changed, but don't update immediately
fIndexerListener.ignoreChanges(false);
setCModelChanged(true);
} else if (delta.getElement() instanceof ITranslationUnit) {
fIndexerListener.ignoreChanges(true);
}
}
if ((flags & (
ICElementDelta.F_CHANGED_PATHENTRY_INCLUDE |
ICElementDelta.F_CHANGED_PATHENTRY_MACRO
)) != 0) {
if (isRelevantProject(delta.getElement().getCProject())) {
return true;
}
}
if ((flags & ICElementDelta.F_CHILDREN) != 0) {
ICElementDelta[] childDeltas= delta.getChangedChildren();
for (int i = 0; i < childDeltas.length; i++) {
if (isRelevantDelta(childDeltas[i])) {
return true;
}
}
}
return false;
}
}
private class IndexerListener implements IIndexerStateListener, IIndexChangeListener {
private boolean fIndexChanged;
private boolean fIgnoreChanges;
/*
* @see org.eclipse.cdt.core.index.IIndexerStateListener#indexChanged(org.eclipse.cdt.core.index.IIndexerStateEvent)
*/
public void indexChanged(IIndexerStateEvent event) {
if (event.indexerIsIdle()) {
if (fIndexChanged || hasCModelChanged()) {
fIndexChanged= false;
if (!fIsReconciling && isEditorActive() && fInitialProcessDone) {
CReconciler.this.scheduleReconciling();
} else {
setCModelChanged(true);
}
}
fIgnoreChanges= false;
}
}
public void ignoreChanges(boolean ignore) {
fIgnoreChanges= ignore;
}
/*
* @see org.eclipse.cdt.core.index.IIndexChangeListener#indexChanged(org.eclipse.cdt.core.index.IIndexChangeEvent)
*/
public void indexChanged(IIndexChangeEvent event) {
if (!fIndexChanged && isRelevantProject(event.getAffectedProject())) {
if (!fIgnoreChanges || event.isCleared() || event.isReloaded() || event.hasNewFile()) {
fIndexChanged= true;
}
}
}
}
/** The reconciler's editor */
private ITextEditor fTextEditor;
/** The part listener */
private IPartListener2 fPartListener;
/** The shell listener */
private ShellListener fActivationListener;
/** The C element changed listener. */
private IElementChangedListener fCElementChangedListener;
/** The indexer listener */
private IndexerListener fIndexerListener;
/** Tells whether the C model might have changed. */
private volatile boolean fHasCModelChanged= false;
/** Tells whether this reconciler's editor is active. */
private volatile boolean fIsEditorActive= true;
/** Tells whether a reconcile is in progress. */
private volatile boolean fIsReconciling= false;
private boolean fInitialProcessDone= false;
private Job fTriggerReconcilerJob;
/**
* Create a reconciler for the given editor and strategy.
*
* @param editor the text editor
* @param strategy the C reconciling strategy
*/
public CReconciler(ITextEditor editor, CCompositeReconcilingStrategy strategy) {
super(strategy, false);
fTextEditor= editor;
}
/*
* @see org.eclipse.jface.text.reconciler.IReconciler#install(org.eclipse.jface.text.ITextViewer)
*/
@Override
public void install(ITextViewer textViewer) {
super.install(textViewer);
fPartListener= new PartListener();
IWorkbenchPartSite site= fTextEditor.getSite();
IWorkbenchWindow window= site.getWorkbenchWindow();
window.getPartService().addPartListener(fPartListener);
fActivationListener= new ActivationListener(textViewer.getTextWidget());
Shell shell= window.getShell();
shell.addShellListener(fActivationListener);
fCElementChangedListener= new ElementChangedListener();
CoreModel.getDefault().addElementChangedListener(fCElementChangedListener);
fIndexerListener= new IndexerListener();
CCorePlugin.getIndexManager().addIndexerStateListener(fIndexerListener);
CCorePlugin.getIndexManager().addIndexChangeListener(fIndexerListener);
fTriggerReconcilerJob= new SingletonJob("Trigger Reconciler", new Runnable() { //$NON-NLS-1$
public void run() {
forceReconciling();
}});
}
/*
* @see org.eclipse.jface.text.reconciler.IReconciler#uninstall()
*/
@Override
public void uninstall() {
fTriggerReconcilerJob.cancel();
IWorkbenchPartSite site= fTextEditor.getSite();
IWorkbenchWindow window= site.getWorkbenchWindow();
window.getPartService().removePartListener(fPartListener);
fPartListener= null;
Shell shell= window.getShell();
if (shell != null && !shell.isDisposed())
shell.removeShellListener(fActivationListener);
fActivationListener= null;
CoreModel.getDefault().removeElementChangedListener(fCElementChangedListener);
fCElementChangedListener= null;
CCorePlugin.getIndexManager().removeIndexerStateListener(fIndexerListener);
CCorePlugin.getIndexManager().removeIndexChangeListener(fIndexerListener);
fIndexerListener= null;
super.uninstall();
}
protected void scheduleReconciling() {
if (!fInitialProcessDone)
return;
if (fTriggerReconcilerJob.cancel()) {
fTriggerReconcilerJob.schedule(50);
}
}
/*
* @see org.eclipse.jface.text.reconciler.AbstractReconciler#forceReconciling()
*/
@Override
protected void forceReconciling() {
if (!fInitialProcessDone)
return;
super.forceReconciling();
}
/*
* @see org.eclipse.jface.text.reconciler.AbstractReconciler#aboutToBeReconciled()
*/
@Override
protected void aboutToBeReconciled() {
CCompositeReconcilingStrategy strategy= (CCompositeReconcilingStrategy)getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE);
strategy.aboutToBeReconciled();
}
/*
* @see org.eclipse.jface.text.reconciler.MonoReconciler#initialProcess()
*/
@Override
protected void initialProcess() {
super.initialProcess();
fInitialProcessDone= true;
if (!fIsReconciling && isEditorActive() && hasCModelChanged()) {
CReconciler.this.scheduleReconciling();
}
}
/*
* @see org.eclipse.jface.text.reconciler.MonoReconciler#process(org.eclipse.jface.text.reconciler.DirtyRegion)
*/
@Override
protected void process(DirtyRegion dirtyRegion) {
fIsReconciling= true;
setCModelChanged(false);
super.process(dirtyRegion);
fIsReconciling= false;
}
/**
* Tells whether the C Model has changed or not.
*
* @return <code>true</code> iff the C Model has changed
*/
private synchronized boolean hasCModelChanged() {
return fHasCModelChanged;
}
/**
* Sets whether the C Model has changed or not.
*
* @param state <code>true</code> iff the C model has changed
*/
private synchronized void setCModelChanged(boolean state) {
fHasCModelChanged= state;
}
/**
* Tells whether this reconciler's editor is active.
*
* @return <code>true</code> iff the editor is active
*/
private synchronized boolean isEditorActive() {
return fIsEditorActive;
}
/**
* Sets whether this reconciler's editor is active.
*
* @param state <code>true</code> iff the editor is active
*/
private synchronized void setEditorActive(boolean active) {
fIsEditorActive= active;
if (!active) {
fTriggerReconcilerJob.cancel();
}
}
public boolean isRelevantElement(ICElement element) {
if (!fInitialProcessDone) {
return false;
}
if (element instanceof IWorkingCopy) {
return false;
}
if (element instanceof ITranslationUnit) {
IEditorInput input= fTextEditor.getEditorInput();
IWorkingCopyManager manager= CUIPlugin.getDefault().getWorkingCopyManager();
IWorkingCopy copy= manager.getWorkingCopy(input);
if (copy == null || copy.getOriginalElement().equals(element)) {
return false;
}
return isRelevantProject(copy.getCProject());
}
return false;
}
private boolean isRelevantProject(ICProject affectedProject) {
if (affectedProject == null) {
return false;
}
IEditorInput input= fTextEditor.getEditorInput();
IWorkingCopyManager manager= CUIPlugin.getDefault().getWorkingCopyManager();
IWorkingCopy copy= manager.getWorkingCopy(input);
if (copy == null) {
return false;
}
if (copy.getCProject().equals(affectedProject)) {
return true;
}
IProject project= copy.getCProject().getProject();
if (project == null) {
return false;
}
try {
IProject[] referencedProjects= project.getReferencedProjects();
for (int i= 0; i < referencedProjects.length; i++) {
project= referencedProjects[i];
if (project.equals(affectedProject.getProject())) {
return true;
}
}
} catch (CoreException exc) {
}
return false;
}
}