blob: aa8ddc5088af2f3b1b51f3a8c690ff0cbb603b89 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.impl;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings;
import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Shows small (16x16 or less) icons as gutters
* Works in places where it's possible to resolve from literal expression
* to an icon image
*
* @author Konstantin Bulenkov
*/
public class IconLineMarkerProvider implements LineMarkerProvider {
@NonNls private static final String JAVAX_SWING_ICON = "javax.swing.Icon";
private static final int ICON_MAX_WEIGHT = 16;
private static final int ICON_MAX_HEIGHT = 16;
private static final int ICON_MAX_SIZE = 2 * 1024 * 1024; //2Kb
private static final List<String> ICON_EXTS = Arrays.asList("png", "ico", "bmp", "gif", "jpg");
//TODO: remove old unused icons from the cache
private final HashMap<String, Pair<Long, Icon>> iconsCache = new HashMap<String, Pair<Long, Icon>>();
@Override
public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) {
if (! DaemonCodeAnalyzerSettings.getInstance().SHOW_SMALL_ICONS_IN_GUTTER) return null;
if (element instanceof PsiAssignmentExpression) {
final PsiExpression lExpression = ((PsiAssignmentExpression)element).getLExpression();
final PsiExpression expr = ((PsiAssignmentExpression)element).getRExpression();
if (lExpression instanceof PsiReferenceExpression) {
PsiElement var = ((PsiReferenceExpression)lExpression).resolve();
if (var instanceof PsiVariable) {
return resolveIconInfo(((PsiVariable)var).getType(), expr);
}
}
}
else if (element instanceof PsiReturnStatement) {
PsiReturnStatement psiReturnStatement = (PsiReturnStatement)element;
final PsiExpression value = psiReturnStatement.getReturnValue();
final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
if (method != null) {
final PsiType returnType = method.getReturnType();
final LineMarkerInfo<PsiElement> result = resolveIconInfo(returnType, value);
if (result != null || !isIconClassType(returnType) || value == null) return result;
if (methodContainsReturnStatementOnly(method)) {
for (PsiReference ref : value.getReferences()) {
final PsiElement field = ref.resolve();
if (field instanceof PsiField) {
return resolveIconInfo(returnType, ((PsiField)field).getInitializer(), psiReturnStatement);
}
}
}
}
}
else if (element instanceof PsiVariable) {
PsiVariable var = (PsiVariable)element;
return resolveIconInfo(var.getType(), var.getInitializer());
}
return null;
}
private static boolean methodContainsReturnStatementOnly(@NotNull PsiMethod method) {
final PsiCodeBlock body = method.getBody();
if (body == null || body.getStatements().length != 1) return false;
return body.getStatements()[0] instanceof PsiReturnStatement;
}
@Nullable
private LineMarkerInfo<PsiElement> resolveIconInfo(PsiType type, PsiExpression initializer) {
return resolveIconInfo(type, initializer, initializer);
}
@Nullable
private LineMarkerInfo<PsiElement> resolveIconInfo(PsiType type, PsiExpression initializer, PsiElement bindingElement) {
if (initializer != null && initializer.isValid() && isIconClassType(type)) {
final Project project = initializer.getProject();
final List<FileReference> refs = new ArrayList<FileReference>();
initializer.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitElement(PsiElement element) {
if (element instanceof PsiLiteralExpression) {
for (PsiReference ref : element.getReferences()) {
if (ref instanceof FileReference) {
refs.add((FileReference)ref);
}
}
}
super.visitElement(element);
}
});
for (FileReference ref : refs) {
final PsiFileSystemItem psiFileSystemItem = ref.resolve();
VirtualFile file = null;
if (psiFileSystemItem == null) {
final ResolveResult[] results = ref.multiResolve(false);
for (ResolveResult result : results) {
final PsiElement element = result.getElement();
if (element instanceof PsiBinaryFile) {
file = ((PsiFile)element).getVirtualFile();
break;
}
}
} else {
file = psiFileSystemItem.getVirtualFile();
}
if (file == null || file.isDirectory()
|| !isIconFileExtension(file.getExtension())
|| file.getLength() > ICON_MAX_SIZE) continue;
final Icon icon = getIcon(file, project);
if (icon != null) {
final Ref<VirtualFile> f = Ref.create(file);
final GutterIconNavigationHandler<PsiElement> navHandler = new GutterIconNavigationHandler<PsiElement>() {
@Override
public void navigate(MouseEvent e, PsiElement elt) {
FileEditorManager.getInstance(project).openFile(f.get(), true);
}
};
return new LineMarkerInfo<PsiElement>(bindingElement, bindingElement.getTextRange(), icon,
Pass.UPDATE_ALL, null, navHandler,
GutterIconRenderer.Alignment.LEFT);
}
}
}
return null;
}
private static boolean isIconFileExtension(String extension) {
return extension != null && ICON_EXTS.contains(extension.toLowerCase());
}
@Override
public void collectSlowLineMarkers(@NotNull List<PsiElement> elements, @NotNull Collection<LineMarkerInfo> result) {
}
private static boolean hasProperSize(Icon icon) {
return icon.getIconHeight() <= ICON_MAX_HEIGHT && icon.getIconWidth() <= ICON_MAX_WEIGHT;
}
@Nullable
private Icon getIcon(VirtualFile file, Project project) {
final String path = file.getPath();
final long stamp = file.getModificationStamp();
Pair<Long, Icon> iconInfo = iconsCache.get(path);
if (iconInfo == null || iconInfo.getFirst() < stamp) {
try {
final Icon icon = createOrFindBetterIcon(file, isIdeaProject(project));
iconInfo = new Pair<Long, Icon>(stamp, hasProperSize(icon) ? icon : null);
iconsCache.put(file.getPath(), iconInfo);
}
catch (Exception e) {//
iconInfo = null;
iconsCache.remove(path);
}
}
return iconInfo == null ? null : iconInfo.getSecond();
}
private static boolean isIdeaProject(Project project) {
if (project == null) return false;
VirtualFile baseDir = project.getBaseDir();
return baseDir != null && (baseDir.findChild("idea.iml") != null || baseDir.findChild("community-main.iml") != null);
}
private static Icon createOrFindBetterIcon(VirtualFile file, boolean tryToFindBetter) throws IOException {
if (tryToFindBetter) {
return IconLoader.findIcon(new File(file.getPath()).toURI().toURL());
}
return new ImageIcon(file.contentsToByteArray());
}
private static boolean isIconClassType(PsiType type) {
return InheritanceUtil.isInheritor(type, JAVAX_SWING_ICON);
}
}