blob: b420ae353f4af1126502ba1257ca101feb2c438d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.internal.ui.text;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.ElementChangedEvent;
import org.eclipse.dltk.core.IElementChangedListener;
import org.eclipse.dltk.core.IModelElementDelta;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.internal.ui.editor.EditorUtility;
import org.eclipse.dltk.internal.ui.editor.ScriptEditor;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.reconciler.IReconcilingStrategy;
import org.eclipse.jface.text.reconciler.MonoReconciler;
import org.eclipse.jface.util.IPropertyChangeListener;
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.IFileEditorInput;
import org.eclipse.ui.IPartListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.spelling.SpellingService;
public class ScriptReconciler extends MonoReconciler {
/**
* Creates a new reconciler.
*
* @param editor
* the editor
* @param strategy
* the reconcile strategy
* @param isIncremental
* <code>true</code> if this is an incremental reconciler
*/
public ScriptReconciler(ITextEditor editor, IReconcilingStrategy strategy,
boolean isIncremental) {
super(strategy, isIncremental);
fTextEditor = editor;
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=63898
// when re-using editors, a new reconciler is set up by the source
// viewer
// and the old one uninstalled. However, the old reconciler may still be
// running.
// To avoid having to reconcilers calling
// CompilationUnitEditor.reconciled,
// we synchronized on a lock object provided by the editor.
// The critical section is really the entire run() method of the
// reconciler
// thread, but synchronizing process() only will keep
// JavaReconcilingStrategy
// from running concurrently on the same editor.
// TODO remove once we have ensured that there is only one reconciler
// per editor.
if (editor instanceof ScriptEditor)
fMutex = ((ScriptEditor) editor).getReconcilerLock();
else
fMutex = new Object(); // Null Object
}
/**
* Internal part listener for activating the reconciler.
*/
private class PartListener implements IPartListener {
@Override
public void partActivated(IWorkbenchPart part) {
if (part == fTextEditor) {
if (hasModelChanged())
ScriptReconciler.this.forceReconciling();
setEditorActive(true);
}
}
@Override
public void partBroughtToTop(IWorkbenchPart part) {
}
@Override
public void partClosed(IWorkbenchPart part) {
}
@Override
public void partDeactivated(IWorkbenchPart part) {
if (part == fTextEditor) {
setModelChanged(false);
setEditorActive(false);
}
}
@Override
public void partOpened(IWorkbenchPart part) {
}
}
/**
* 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;
}
@Override
public void shellActivated(ShellEvent e) {
if (!fControl.isDisposed() && fControl.isVisible()) {
if (hasModelChanged())
ScriptReconciler.this.forceReconciling();
setEditorActive(true);
}
}
@Override
public void shellDeactivated(ShellEvent e) {
if (!fControl.isDisposed() && fControl.getShell() == e.getSource()) {
setModelChanged(false);
setEditorActive(false);
}
}
}
/**
* Internal script element changed listener
*
* @since 3.0
*/
private class ElementChangedListener implements IElementChangedListener {
@Override
public void elementChanged(ElementChangedEvent event) {
if (isRunningInReconcilerThread())
return;
if (canIgnore(event.getDelta().getAffectedChildren()))
return;
setModelChanged(true);
if (isEditorActive())
ScriptReconciler.this.forceReconciling();
}
/**
* Check whether the given delta has been sent when saving this
* reconciler's editor.
*
* @param delta
* the deltas
* @return <code>true</code> if the given delta
* @since 5.0
*/
private boolean canIgnore(IModelElementDelta[] delta) {
if (delta.length != 1)
return false;
// become working copy
if (delta[0].getFlags() == IModelElementDelta.F_PRIMARY_WORKING_COPY)
return true;
// save
if (delta[0].getFlags() == IModelElementDelta.F_PRIMARY_RESOURCE
&& delta[0].getElement().equals(fReconciledElement))
return true;
return canIgnore(delta[0].getAffectedChildren());
}
}
/**
* Internal resource change listener.
*
* @since 3.0
*/
class ResourceChangeListener implements IResourceChangeListener {
private IResource getResource() {
IEditorInput input = fTextEditor.getEditorInput();
if (input instanceof IFileEditorInput) {
IFileEditorInput fileInput = (IFileEditorInput) input;
return fileInput.getFile();
}
return null;
}
@Override
public void resourceChanged(IResourceChangeEvent e) {
if (isRunningInReconcilerThread())
return;
IResourceDelta delta = e.getDelta();
IResource resource = getResource();
if (delta != null && resource != null) {
IResourceDelta child = delta.findMember(resource.getFullPath());
if (child != null) {
IMarkerDelta[] deltas = child.getMarkerDeltas();
int i = deltas.length;
while (--i >= 0) {
if (deltas[i].isSubtypeOf(IMarker.PROBLEM)) {
forceReconciling();
return;
}
}
}
}
}
}
/** The reconciler's editor */
private ITextEditor fTextEditor;
/** The part listener */
private IPartListener fPartListener;
/** The shell listener */
private ShellListener fActivationListener;
/**
* The mutex that keeps us from running multiple reconcilers on one editor.
*/
private Object fMutex;
/**
* The script element changed listener.
*
* @since 3.0
*/
private IElementChangedListener fScriptElementChangedListener;
/**
* Tells whether the script model sent out a changed event.
*
* @since 3.0
*/
private volatile boolean fHasModelChanged = true;
/**
* Tells whether this reconciler's editor is active.
*
* @since 3.1
*/
private volatile boolean fIsEditorActive = true;
/**
* The resource change listener.
*
* @since 3.0
*/
private IResourceChangeListener fResourceChangeListener;
/**
* The property change listener.
*
* @since 3.3
*/
private IPropertyChangeListener fPropertyChangeListener;
private boolean fIninitalProcessDone = false;
/**
* The element that this reconciler reconciles.
*
* @since 3.4
*/
private ISourceModule fReconciledElement;
@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);
fScriptElementChangedListener = new ElementChangedListener();
DLTKCore.addElementChangedListener(fScriptElementChangedListener);
fResourceChangeListener = new ResourceChangeListener();
ResourcesPlugin.getWorkspace().addResourceChangeListener(
fResourceChangeListener);
final IPreferenceStore store = getCombinedPreferenceStore();
if (store != null) {
fPropertyChangeListener = event -> {
if (SpellingService.PREFERENCE_SPELLING_ENABLED
.equals(event.getProperty())
|| SpellingService.PREFERENCE_SPELLING_ENGINE
.equals(event.getProperty()))
forceReconciling();
};
store.addPropertyChangeListener(fPropertyChangeListener);
}
fReconciledElement = EditorUtility.getEditorInputModelElement(
fTextEditor, false);
}
protected IPreferenceStore getCombinedPreferenceStore() {
// JavaPlugin.getDefault().getCombinedPreferenceStore()
// TODO implement when needed
return null;
}
@Override
public void uninstall() {
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;
DLTKCore.removeElementChangedListener(fScriptElementChangedListener);
fScriptElementChangedListener = null;
ResourcesPlugin.getWorkspace().removeResourceChangeListener(
fResourceChangeListener);
fResourceChangeListener = null;
if (fPropertyChangeListener != null) {
final IPreferenceStore store = getCombinedPreferenceStore();
if (store != null) {
store.removePropertyChangeListener(fPropertyChangeListener);
}
fPropertyChangeListener = null;
}
super.uninstall();
}
@Override
protected void forceReconciling() {
if (!fIninitalProcessDone)
return;
super.forceReconciling();
ScriptCompositeReconcilingStrategy strategy = (ScriptCompositeReconcilingStrategy) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE);
strategy.notifyListeners(false);
}
@Override
protected void aboutToBeReconciled() {
ScriptCompositeReconcilingStrategy strategy = (ScriptCompositeReconcilingStrategy) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE);
strategy.aboutToBeReconciled();
}
@Override
protected void reconcilerReset() {
super.reconcilerReset();
ScriptCompositeReconcilingStrategy strategy = (ScriptCompositeReconcilingStrategy) getReconcilingStrategy(IDocument.DEFAULT_CONTENT_TYPE);
strategy.notifyListeners(true);
}
@Override
protected void initialProcess() {
synchronized (fMutex) {
super.initialProcess();
}
fIninitalProcessDone = true;
}
/**
* Tells whether the script Model has changed or not.
*
* @return <code>true</code> iff the script Model has changed
* @since 3.0
*/
private synchronized boolean hasModelChanged() {
return fHasModelChanged;
}
/**
* Sets whether the script Model has changed or not.
*
* @param state
* <code>true</code> iff the script model has changed
* @since 3.0
*/
private synchronized void setModelChanged(boolean state) {
fHasModelChanged = state;
}
/**
* Tells whether this reconciler's editor is active.
*
* @return <code>true</code> iff the editor is active
* @since 3.1
*/
private synchronized boolean isEditorActive() {
return fIsEditorActive;
}
/**
* Sets whether this reconciler's editor is active.
*
* @param state
* <code>true</code> iff the editor is active
* @since 3.1
*/
private synchronized void setEditorActive(boolean state) {
fIsEditorActive = state;
}
}