/******************************************************************************* * Copyright (c) 2000, 2017 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 * *******************************************************************************/ package org.eclipse.dltk.ui; import java.util.Iterator; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.ListenerList; import org.eclipse.dltk.core.IExternalSourceModule; import org.eclipse.dltk.core.IModelElement; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.core.ISourceRange; import org.eclipse.dltk.core.ISourceReference; import org.eclipse.dltk.core.ModelException; import org.eclipse.dltk.internal.core.SourceModule; import org.eclipse.dltk.internal.ui.editor.ExternalStorageEditorInput; import org.eclipse.dltk.ui.viewsupport.IProblemChangedListener; import org.eclipse.dltk.ui.viewsupport.ImageDescriptorRegistry; import org.eclipse.dltk.ui.viewsupport.ImageImageDescriptor; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.ILabelDecorator; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ILightweightLabelDecorator; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.MarkerAnnotation; /** * LabelDecorator that decorates an element's image with error and warning * overlays that represent the severity of markers attached to the element's * underlying resource. To see a problem decoration for a marker, the marker * needs to be a subtype of <code>IMarker.PROBLEM</code>. * <p> * <b>Important</b>: Although this decorator implements * ILightweightLabelDecorator, do not contribute this class as a decorator to * the <code>org.eclipse.ui.decorators</code> extension. Only use this class in * your own views and label providers. */ public class ProblemsLabelDecorator implements ILabelDecorator, ILightweightLabelDecorator { /** * This is a special <code>LabelProviderChangedEvent</code> carrying * additional information whether the event origins from a maker change. * <p> * <code>ProblemsLabelChangedEvent</code>s are only generated by <code> * ProblemsLabelDecorator</code>s. * </p> */ public static class ProblemsLabelChangedEvent extends LabelProviderChangedEvent { private static final long serialVersionUID = 1L; private boolean fMarkerChange; /** * Note: This constructor is for internal use only. Clients should not * call this constructor. * * @param eventSource * the base label provider * @param changedResource * the changed resources * @param isMarkerChange * <code>true<code> if the change is a marker change; otherwise * <code>false</code> */ public ProblemsLabelChangedEvent(IBaseLabelProvider eventSource, IResource[] changedResource, boolean isMarkerChange) { super(eventSource, changedResource); fMarkerChange = isMarkerChange; } /** * Returns whether this event origins from marker changes. If * <code>false</code> an annotation model change is the origin. In this * case viewers not displaying working copies can ignore these events. * * @return if this event origins from a marker change. */ public boolean isMarkerChange() { return fMarkerChange; } } private static final int ERRORTICK_WARNING = ScriptElementImageDescriptor.WARNING; private static final int ERRORTICK_ERROR = ScriptElementImageDescriptor.ERROR; private ImageDescriptorRegistry fRegistry; private boolean fUseNewRegistry = false; private IProblemChangedListener fProblemChangedListener; private ListenerList<ILabelProviderListener> fListeners; private ISourceRange fCachedRange; /** * Creates a new <code>ProblemsLabelDecorator</code>. */ public ProblemsLabelDecorator() { this(null); fUseNewRegistry = true; } /** * Note: This constructor is for internal use only. Clients should not call * this constructor. * * @param registry * The registry to use or <code>null</code> to use the Script * plugin's image registry */ public ProblemsLabelDecorator(ImageDescriptorRegistry registry) { fRegistry = registry; fProblemChangedListener = null; } private ImageDescriptorRegistry getRegistry() { if (fRegistry == null) { fRegistry = fUseNewRegistry ? new ImageDescriptorRegistry() : DLTKUIPlugin.getImageDescriptorRegistry(); } return fRegistry; } @Override public String decorateText(String text, Object element) { return text; } @Override public Image decorateImage(Image image, Object obj) { int adornmentFlags = computeAdornmentFlags(obj); if (adornmentFlags != 0) { ImageDescriptor baseImage = new ImageImageDescriptor(image); Rectangle bounds = image.getBounds(); return getRegistry().get(new ScriptElementImageDescriptor(baseImage, adornmentFlags, new Point(bounds.width, bounds.height))); } return image; } /** * Note: This method is for internal use only. Clients should not call this * method. * * @param obj * the element to compute the flags for * * @return the adornment flags */ protected int computeAdornmentFlags(Object obj) { try { if (obj instanceof IModelElement) { IModelElement element = (IModelElement) obj; int type = element.getElementType(); switch (type) { case IModelElement.SCRIPT_MODEL: case IModelElement.SCRIPT_PROJECT: case IModelElement.PROJECT_FRAGMENT: return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_INFINITE, null); case IModelElement.SCRIPT_FOLDER: /** * At the moment IScriptFolder refers to the current folder * only (used in flat view mode). If you want problem status * with subfolders then use * {@link org.eclipse.dltk.internal.ui.navigator.TreeHierarchyLayoutProblemsDecorator} * which handles it as * {@link org.eclipse.core.resources.IFolder} */ case IModelElement.SOURCE_MODULE: return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_ONE, null); case IModelElement.TYPE: case IModelElement.METHOD: case IModelElement.FIELD: ISourceModule cu = (ISourceModule) element .getAncestor(IModelElement.SOURCE_MODULE); if (cu != null) { ISourceReference ref = (type == IModelElement.SOURCE_MODULE) ? null : (ISourceReference) element; // The assumption is that only source elements in // compilation unit can have markers IAnnotationModel model = isInScriptAnnotationModel(cu); int result = 0; if (model != null) { // open in Script editor: look at annotation model result = getErrorTicksFromAnnotationModel(model, ref); } else { result = getErrorTicksFromMarkers(cu.getResource(), IResource.DEPTH_ONE, ref); } fCachedRange = null; return result; } break; default: } } else if (obj instanceof IResource) { return getErrorTicksFromMarkers((IResource) obj, IResource.DEPTH_INFINITE, null); } } catch (CoreException e) { if (e instanceof ModelException) { if (((ModelException) e).isDoesNotExist()) { return 0; } } if (e.getStatus().getCode() == IResourceStatus.MARKER_NOT_FOUND) { return 0; } DLTKUIPlugin.log(e); } return 0; } private int getErrorTicksFromMarkers(IResource res, int depth, ISourceReference sourceElement) throws CoreException { if (res == null || !res.isAccessible()) { return 0; } int info = 0; IMarker[] markers = res.findMarkers(IMarker.PROBLEM, true, depth); if (markers != null) { for (int i = 0; i < markers.length && (info != ERRORTICK_ERROR); i++) { IMarker curr = markers[i]; if (sourceElement == null || isMarkerInRange(curr, sourceElement)) { int priority = curr.getAttribute(IMarker.SEVERITY, -1); if (priority == IMarker.SEVERITY_WARNING) { info = ERRORTICK_WARNING; } else if (priority == IMarker.SEVERITY_ERROR) { info = ERRORTICK_ERROR; } } } } return info; } private boolean isMarkerInRange(IMarker marker, ISourceReference sourceElement) throws CoreException { if (marker.isSubtypeOf(IMarker.TEXT)) { int pos = marker.getAttribute(IMarker.CHAR_START, -1); return isInside(pos, sourceElement); } return false; } private IAnnotationModel isInScriptAnnotationModel(ISourceModule original) { if (original.isWorkingCopy()) { if (original instanceof SourceModule) { FileEditorInput editorInput = new FileEditorInput( (IFile) original.getResource()); return DLTKUIPlugin.getDefault() .getSourceModuleDocumentProvider() .getAnnotationModel(editorInput); } else if (original instanceof IExternalSourceModule) { ExternalStorageEditorInput editorInput = new ExternalStorageEditorInput( (IExternalSourceModule) original); return DLTKUIPlugin.getDefault() .getSourceModuleDocumentProvider() .getAnnotationModel(editorInput); } } return null; } private int getErrorTicksFromAnnotationModel(IAnnotationModel model, ISourceReference sourceElement) throws CoreException { int info = 0; Iterator<Annotation> iter = model.getAnnotationIterator(); while ((info != ERRORTICK_ERROR) && iter.hasNext()) { Annotation annot = iter.next(); IMarker marker = isAnnotationInRange(model, annot, sourceElement); if (marker != null) { int priority = marker.getAttribute(IMarker.SEVERITY, -1); if (priority == IMarker.SEVERITY_WARNING) { info = ERRORTICK_WARNING; } else if (priority == IMarker.SEVERITY_ERROR) { info = ERRORTICK_ERROR; } } } return info; } private IMarker isAnnotationInRange(IAnnotationModel model, Annotation annot, ISourceReference sourceElement) throws CoreException { if (annot instanceof MarkerAnnotation) { if (sourceElement == null || isInside(model.getPosition(annot), sourceElement)) { IMarker marker = ((MarkerAnnotation) annot).getMarker(); if (marker.exists() && marker.isSubtypeOf(IMarker.PROBLEM)) { return marker; } } } return null; } private boolean isInside(Position pos, ISourceReference sourceElement) throws CoreException { return pos != null && isInside(pos.getOffset(), sourceElement); } /** * Tests if a position is inside the source range of an element. * * @param pos * Position to be tested. * @param sourceElement * Source element (must be a IModelElement) * @return boolean Return <code>true</code> if position is located inside * the source element. * @throws CoreException * Exception thrown if element range could not be accessed. */ protected boolean isInside(int pos, ISourceReference sourceElement) throws CoreException { if (fCachedRange == null) { fCachedRange = sourceElement.getSourceRange(); } ISourceRange range = fCachedRange; if (range != null) { int rangeOffset = range.getOffset(); return (rangeOffset <= pos && rangeOffset + range.getLength() > pos); } return false; } @Override public void dispose() { if (fProblemChangedListener != null) { DLTKUIPlugin.getDefault().getProblemMarkerManager() .removeListener(fProblemChangedListener); fProblemChangedListener = null; } if (fRegistry != null && fUseNewRegistry) { fRegistry.dispose(); } } @Override public boolean isLabelProperty(Object element, String property) { return true; } @Override public void addListener(ILabelProviderListener listener) { if (fListeners == null) { fListeners = new ListenerList<>(); } fListeners.add(listener); if (fProblemChangedListener == null) { fProblemChangedListener = (changedResources, isMarkerChange) -> fireProblemsChanged(changedResources, isMarkerChange); DLTKUIPlugin.getDefault().getProblemMarkerManager() .addListener(fProblemChangedListener); } } @Override public void removeListener(ILabelProviderListener listener) { if (fListeners != null) { fListeners.remove(listener); if (fListeners.isEmpty() && fProblemChangedListener != null) { DLTKUIPlugin.getDefault().getProblemMarkerManager() .removeListener(fProblemChangedListener); fProblemChangedListener = null; } } } private void fireProblemsChanged(IResource[] changedResources, boolean isMarkerChange) { if (fListeners != null && !fListeners.isEmpty()) { LabelProviderChangedEvent event = new ProblemsLabelChangedEvent( this, changedResources, isMarkerChange); for (ILabelProviderListener listener : fListeners) { listener.labelProviderChanged(event); } } } @Override public void decorate(Object element, IDecoration decoration) { int adornmentFlags = computeAdornmentFlags(element); if (adornmentFlags == ERRORTICK_ERROR) { decoration.addOverlay(DLTKPluginImages.DESC_OVR_ERROR); } else if (adornmentFlags == ERRORTICK_WARNING) { decoration.addOverlay(DLTKPluginImages.DESC_OVR_WARNING); } } }